• Unity Shader学习(六)实现雷达扫描效果


    本期主要通过shader实现雷达扫描效果,如下所示
    在这里插入图片描述
    根据效果,我们需要先画一个轮廓圆,代码如下

    Shader "Unlit/shader7"
    { 
        ///鼠标移动正方形
        Properties
        {
        _Center("Center",Vector)=(0,0,0,0)
        _Raduis("Radius",float)=0.1
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
    
                #include "UnityCG.cginc"
            struct v2f{
                float4 vertex:SV_POSITION;
                float4 position:TEXCOORD1;
                float2 uv:TEXCOORD;
            };
            v2f vert(appdata_base v){
                v2f o;
                o.vertex=UnityObjectToClipPos(v.vertex);
                o.position=v.vertex;
                o.uv=v.texcoord;
                return o;
            }
            //中心点
            float4 _Center;
            //半径
            float _Raduis;
            //绘制正方形
            float rect(float2 pt,float2 size,float2 center){
                float2 p=pt-center;
                float2 halfsize=size*0.5;
                float hotz=step(-halfsize.x,p.x)-step(halfsize.x,p.x);
                float vert=step(-halfsize.y,p.y)-step(halfsize.y,p.y);
                return hotz*vert;
            }
            //普通的实心圆
            float circle(float2 pt,float2 center,float radius){
                float2 p=pt-center;
                return 1.0-step(radius,length(p));
            }
            //边缘软化圆
              float circle(float2 pt,float2 center,float radius,bool soften){
                float2 p=pt-center;
                float edge=(soften)?radius*0.05:0.0;
                return 1.0-smoothstep(radius-edge,radius+edge,length(p));
            }
             //轮廓圆
            float circle(float2 pt,float2 center,float radius,float line_width){
                float2 p=pt-center;
                float len=length(p);
                float half_line_width=line_width/2.0;
    
                return step(radius-half_line_width,len)-step(radius+half_line_width,len);
            }
    
                fixed4 frag (v2f i) : SV_Target
                {
                    float2 pos=i.position*2;
                    float2 size=0.2;
                    float outlineCircle=circle(pos,_Center,_Raduis,0.01f);
                    fixed3 col=fixed3(1,1,0)*outlineCircle;
                    return fixed4(col,1.0);
                }
                ENDCG
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    代码中的轮廓圆,是在实心圆的基础上进行绘制,实心圆需要绘制半径内所有点的颜色信息,而轮廓圆,只需要绘制轮廓线即可,因此只需要取半径radius+half_line_width和radius-half_line_width范围之内的点即可,如下图所示
    ,只要判断出在该点范围内进行输出即可。
    在这里插入图片描述
    代码中使用到了两个step函数,可以将结果控制在该范围内,如下图所示,只要是在该范围内,就输出1,不在该范围,都输出0。
    在这里插入图片描述
    在以上代码的基础上,我们还需要画线,线的代码如下所示

     //画线
            float onLine(float a,float b,float line_width,float edge_thickness){
                float half_line_width=line_width*0.5;
                return smoothstep(a-half_line_width-edge_thickness,a-half_line_width,b)-smoothstep(a+half_line_width,a+half_line_width+edge_thickness,b);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在该函数中,使用了smoothstep函数,该函数的意义如下

    float smoothstep(float a, float b, float x) 
    {
      x = clamp((x - a) / (b- a), 0.0, 1.0); 
      return x * x * (3 - 2 * x);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当ab时,返回1 否则在a和b之间平滑过渡
    当a>b,x>a时,返回0,当x了解shader函数

    回到画线的函数中,a和b分别为线的x,y点位置,line_width为线的宽度,edge_thickness为线的厚度,通过smoothstep判断点是否在该范围内,这里根据的是x和y的坐标来判定的,稍微有一点难理解,最好就是自己画图输入参数证明一下。
    完成画圈和直线的函数之后,在片元着色器里面处理一下

    
                fixed4 frag (v2f i) : SV_Target
                {
                    float2 pos=i.position*2;
                    float2 size=0.2;
                    float ouline1=onLine(i.uv.y,0.5,0.002,0.001)*_AxisColor;
                    float ouline2=onLine(i.uv.x,0.5,0.002,0.001)*_AxisColor;
                    
                    float outlineCircle=circle(pos,_Center,_Raduis,0.01f);
                    fixed3 col=ouline1+ouline2+outlineCircle;
                    col+=circle(pos,_Center,_Raduis-0.1,0.01f);
                    col+=circle(pos,_Center,_Raduis-0.2,0.01f);
                    return fixed4(col,1.0);
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以得到以下图形:
    在这里插入图片描述
    接下来,画扫描线,需要用到一个非常关键的内置函数_Time,该函数可以实现循环输出数值的一个效果
    t是自该场景加载开始所经过的时间,4个分量分别是 (t/20, t, t2, t3)
    _Time float4 time (t/20, t, t2, t3)

       //扫描线
            float sweep(float2 pt,float2 center,float radius,float line_width,float edge_thickness){
                float2 d=pt-center;
                float theta=_Time.z;
                float2 p=float2(cos(theta),-sin(theta))*radius;
                float h=clamp(dot(d,p)/dot(p,p),0.0,1.0);
                float l=length(d-p*h);
                return 1.0-smoothstep(line_width,line_width+edge_thickness,l);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    扫描线主要是根据向量d的位置,计算到循环出来的p点的距离,然后判断距离是否在范围内,在这个过程中使用了点乘dot函数计算点d投影到p的长度,该长度h使用了clamp函数进行限制,限制范围在0-1内。得到h后,计算点到p的距离,先使用d-p*h获得该投影点的向量,再使用length函数。得到的效果如下:
    在这里插入图片描述
    最后,再加上扫描的延迟阴影,整体代码如下:

        //扫描线
            float sweep(float2 pt,float2 center,float radius,float line_width,float edge_thickness){
                float2 d=pt-center;
                float theta=_Time.z;
                float2 p=float2(cos(theta),-sin(theta))*radius;
                float h=clamp(dot(d,p)/dot(p,p),0.0,1.0);
                float l=length(d-p*h);
                float gradient=0.0;
                const float gradient_angle=UNITY_PI*0.5;
                if(length(d)<radius){
                    float angle=fmod(theta+atan2(d.y,d.x),UNITY_TWO_PI);
                    gradient=clamp(gradient_angle-angle,0,gradient_angle)/gradient_angle*0.5;
                    
    
                return gradient+1.0-smoothstep(line_width,line_width+edge_thickness,l);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    延迟阴影这部分主要是针对点d与p之间的角度进行处理,原理也是对角度进行判断和计算,涉及到较多数学问题,后面有机会再细讲,这里只讲用到的函数
    fmod:返回a / b的浮点余数
    atan2:返回反正切值
    完整代码:

    Shader "Unlit/shader7"
    { 
        ///鼠标移动正方形
        Properties
        {
        _Center("Center",Vector)=(0,0,0,0)
        _Raduis("Radius",float)=0.1
        _AxisColor("AxisColor",Color)=(1,1,0,1)
        }
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
    
                #include "UnityCG.cginc"
            struct v2f{
                float4 vertex:SV_POSITION;
                float4 position:TEXCOORD1;
                float2 uv:TEXCOORD;
                float4 screenPos:TEXCOORD2;
            };
            v2f vert(appdata_base v){
                v2f o;
                o.vertex=UnityObjectToClipPos(v.vertex);
                o.position=v.vertex;
                o.uv=v.texcoord;
                o.screenPos=ComputeScreenPos(o.vertex);
                return o;
            }
            //中心点
            float4 _Center;
            //半径
            float _Raduis;
    
            fixed4 _AxisColor;
            //绘制正方形
            float rect(float2 pt,float2 size,float2 center){
                float2 p=pt-center;
                float2 halfsize=size*0.5;
                float hotz=step(-halfsize.x,p.x)-step(halfsize.x,p.x);
                float vert=step(-halfsize.y,p.y)-step(halfsize.y,p.y);
                return hotz*vert;
            }
            //普通的实心圆
            float circle(float2 pt,float2 center,float radius){
                float2 p=pt-center;
                return 1.0-step(radius,length(p));
            }
            //边缘软化圆
              float circle(float2 pt,float2 center,float radius,bool soften){
                float2 p=pt-center;
                float edge=(soften)?radius*0.05:0.0;
                return 1.0-smoothstep(radius-edge,radius+edge,length(p));
            }
             //轮廓圆
            float circle(float2 pt,float2 center,float radius,float line_width){
                float2 p=pt-center;
                float len=length(p);
                float half_line_width=line_width/2.0;
    
                return step(radius-half_line_width,len)-step(radius+half_line_width,len);
            }
            //画线
            float onLine(float a,float b,float line_width,float edge_thickness){
                float half_line_width=line_width*0.5;
                return smoothstep(a-half_line_width-edge_thickness,a-half_line_width,b)-smoothstep(a+half_line_width,a+half_line_width+edge_thickness,b);
            }
            //扫描线
            float sweep(float2 pt,float2 center,float radius,float line_width,float edge_thickness){
                float2 d=pt-center;
                float theta=_Time.z;
                float2 p=float2(cos(theta),-sin(theta))*radius;
                float h=clamp(dot(d,p)/dot(p,p),0.0,1.0);
                float l=length(d-p*h);
                float gradient=0.0;
                const float gradient_angle=UNITY_PI*0.5;
                if(length(d)<radius){
                    float angle=fmod(theta+atan2(d.y,d.x),UNITY_TWO_PI);
                    gradient=clamp(gradient_angle-angle,0,gradient_angle)/gradient_angle*0.5;
                }
                return gradient+1.0-smoothstep(line_width,line_width+edge_thickness,l);
            }
            
    
                fixed4 frag (v2f i) : SV_Target
                {
                    float2 pos=i.position*2;
                    float2 size=0.2;
                    float ouline1=onLine(i.uv.y,0.5,0.002,0.001)*_AxisColor;
                    float ouline2=onLine(i.uv.x,0.5,0.002,0.001)*_AxisColor;
                    
                    float outlineCircle=circle(pos,_Center,_Raduis,0.01f);
                    fixed3 col=ouline1+ouline2+outlineCircle;
                    col+=circle(pos,_Center,_Raduis-0.1,0.01f);
                    col+=circle(pos,_Center,_Raduis-0.2,0.01f);
                    col+=sweep(pos,_Center,_Raduis,0.002,0.001)*_AxisColor;
                    return fixed4(col,1.0);
                }
                ENDCG
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
  • 相关阅读:
    UPC-E条码的介绍及制作方法
    typename、typedef、using对比
    Node.js 未来发展趋势
    H3C IMC dynamiccontent.properties.xhtm 远程命令执行
    果断收藏!考完PMP还能学什么?一文解答你的疑惑
    JDK的安装
    关系数据库SQL五条经验教训
    JAVA:面向对象++封装+继承【详细】
    CSDN客诉周报第11期|修复6个重大bug,解决32个次要bug
    语音信号处理(二): 发声生理、听觉生理与听觉心理
  • 原文地址:https://blog.csdn.net/qq_14942529/article/details/125900941