Unity&Shader基础篇-绘制网格+圆盘

发表于2016-11-24
评论0 3.7k浏览

一、前言

尊重原创,转载请注明出处凯尔八阿哥专栏

上一章点击打开链接中已经画出了一个棋盘网格,首先来完善一下这个画网格的Shader,添加属性,属性包括网格的线的宽度,网格的颜色等。代码如下:

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
[/size][/font][font=宋体][size=4]Shader "Unlit/Chapter2-2"
{       Properties
        {
                _backgroundColor("面板背景色",Color) = (1.0,1.0,1.0,1.0)
                _axesColor("坐标轴的颜色",Color) = (0.0,0.0,1.0)
                _gridColor("网格的颜色",Color) = (0.5,0.5,0.5)
                _tickWidth("网格的密集程度",Range(0.1,1))=0.1
                _gridWidth("网格的宽度",Range(0.0001,0.01))=0.008
                _axesXWidth("x轴的宽度",Range(0.0001,0.01))=0.006
                _axesYWidth("y轴的宽度",Range(0.0001,0.01))=0.007
        }
        SubShader
        {
                //去掉遮挡和深度缓冲
                Cull Off
                ZWrite Off
                //开启深度测试
                ZTest Always
  
                CGINCLUDE
                //添加一个计算方法
                float mod(float a,float b)
                {
                        //floor(x)方法是Cg语言内置的方法,返回小于x的最大的整数
                        return a - b*floor(a / b);
                }
                ENDCG
  
                Pass
                {
                        CGPROGRAM
                        //敲代码的时候要注意:“CGPROGRAM”和“#pragma...”中的拼写不同,真不知道“pragma”是什么单词
                        #pragma vertex vert
                        #pragma fragment frag
  
                        #include "UnityCG.cginc"
  
                        uniform float4 _backgroundColor;
                        uniform float4 _axesColor;
                        uniform float4 _gridColor;
                        uniform float _tickWidth;
                        uniform float _gridWidth;
                        uniform float _axesXWidth;
                        uniform float _axesYWidth;
  
                        struct appdata
                        {
                                float4 vertex:POSITION;
                                float2 uv:TEXCOORD0;
                        };
                        struct v2f
                        {
                                float2 uv:TEXCOORD0;
                                float4 vertex:SV_POSITION;
                        };
                        v2f vert(appdata v)
                        {
                                v2f o;
                                o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
                                o.uv = v.uv;
                                return o;
                        }
  
                        fixed4 frag(v2f i) :SV_Target
                        {
                                //将坐标的中心从左下角移动到网格的中心
                                fixed2 r = 2.0*fixed2(i.uv.x - 0.5,i.uv.y - 0.5);
                                fixed3 backgroundColor = _backgroundColor.xyz;
                                fixed3 axesColor = _axesColor.xyz;
                                fixed3 gridColor = _gridColor.xyz;
  
                                fixed3 pixel = backgroundColor;
  
                                //定义网格的的宽度
                                const float tickWidth = _tickWidth;
                                if (mod(r.x, tickWidth) < _gridWidth)
                                {
                                        pixel = gridColor;
                                }
                                if (mod(r.y, tickWidth) < _gridWidth)
                                {
                                        pixel = gridColor;
                                }
  
                                //画两个坐标轴
                                if (abs(r.x) < _axesXWidth)
                                {
                                        pixel = axesColor;
                                }
                                if (abs(r.y) < _axesYWidth)
                                {
                                        pixel = axesColor;
                                }
  
                                return fixed4(pixel, 1.0);
                        }
                                ENDCG
                }
  
        }
  
}



通过以下代码将网格的轴心移动到中心位置
?

1
fixed2 r = 2.0*fixed2(i.uv.x - 0.5,i.uv.y - 0.5);

得到的效果图如图所示:

二、画一个圆盘

原理:圆的方程为(x-a)^2+(y-b)^2=r^2,根据方程即可以添加如下方法来计算预定圆盘的颜色值信息

?
1
2
3
4
5
6
7
8
9
10
//添加第二个计算方法,根据半径,原点、原点的偏移量r和颜色来绘制圆盘       
                fixed3 disk(fixed2 r,fixed2 center,fixed radius,fixed3 color,fixed3 pixel)
                {
                        fixed3 col=pixel;
                        if (length(r - center) < radius)
                        {
                                col = color;
                        }
                        return col;
                }

效果如图所示:

完整的代码如下:

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
Shader "Unlit/Chapter3-2"
{
        Properties
        {
                _backgroundColor("面板背景色",Color) = (1.0,1.0,1.0,1.0)
                _col1("圆盘1的颜色",Color) = (0.216, 0.471, 0.698) // blue
                _col2("圆盘2的颜色",Color) = (1.00, 0.329, 0.298) // red
                _col3("圆盘3的颜色",Color) = (0.867, 0.910, 0.247) // yellow
  
                _center2X("第三个圆盘的原点X的位置",Range(0.0,1.0)) = 0.9
                _center2Y("第三个圆盘的原点Y的位置",Range(0.0,1.0)) = -0.4
  
                _radius1("第一个圆盘的半径",Range(0.1,1)) = 0.8
                _radius2("第二个圆盘的半径", Range(0.1, 1)) = 0.3
                _radius3("第三个圆盘的半径", Range(0.1, 1)) = 0.6
  
        }
        SubShader
        {
                //去掉遮挡和深度缓冲
                Cull Off
                ZWrite Off
                //开启深度测试
                ZTest Always
  
                CGINCLUDE
                //添加一个计算方法
                float mod(float a,float b)
                {
                        //floor(x)方法是Cg语言内置的方法,返回小于x的最大的整数
                        return a - b*floor(a / b);
                }
  
                //添加第二个计算方法,根据半径,原点、原点的偏移量r和颜色来绘制圆盘       
                fixed3 disk(fixed2 r,fixed2 center,fixed radius,fixed3 color,fixed3 pixel)
                {
                        fixed3 col=pixel;
                        if (length(r - center) < radius)
                        {
                                col = color;
                        }
                        return col;
                }
                ENDCG
  
                Pass
                {
                        CGPROGRAM
                        //敲代码的时候要注意:“CGPROGRAM”和“#pragma...”中的拼写不同,真不知道“pragma”是什么单词
                        #pragma vertex vert
                        #pragma fragment frag
  
                        #include "UnityCG.cginc"
  
                        uniform float4 _backgroundColor;
                        uniform float4 _col1;
                        uniform float4 _col2;
                        uniform float4 _col3;
                        uniform float _radius1;
                        uniform float _radius2;
                        uniform float _radius3;
                        uniform float _center2X;
                        uniform float _center2Y;
  
                struct appdata
                {
                        float4 vertex:POSITION;
                        float2 uv:TEXCOORD0;
                };
                struct v2f
                {
                        float2 uv:TEXCOORD0;
                        float4 vertex:SV_POSITION;
                };
                v2f vert(appdata v)
                {
                        v2f o;
                        o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
                        o.uv = v.uv;
                        return o;
                }
          
                fixed4 frag(v2f i) :SV_Target
                {
                        float2 r = 2.0*(i.uv - 0.5);
                        float aspectRatio = _ScreenParams.x / _ScreenParams.y;
                        r.x *= aspectRatio;
  
                        fixed3 backgroundColor = _backgroundColor.xyz;
                        fixed3 col1 = _col1.xyz;
                        fixed3 col2 = _col2.xyz;
                        fixed3 col3 = _col3.xyz;
  
                        fixed3 pixel = _backgroundColor;
  
                        //画第一个圆盘
                        pixel = disk(r,fixed2(0.1,0.3), _radius1,col3,pixel);
                        //画第二个圆盘
                        pixel= disk(r, fixed2(_center2X, _center2Y), _radius2, col2, pixel);
                        //画第三个圆盘
                        pixel= disk(r, fixed2(-0.8, 0.6), _radius3, col1, pixel);
                        return fixed4(pixel, 1.0);
                }
  
                ENDCG
        }
  
        }
  
}

三、在网格中绘制圆盘

1、根据2中绘制圆盘的原理,只要将a和b通过C#代码传递参数给Shader来控制即可,这个参数就是鼠标点击时候的坐标位置。当然这个位置要经过计算才能应用到网格坐标中,网格坐标的范围为-1~1,通过代码来转换:
基本上和Shader代码中的计算方法相同,得到的效果图如图所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
void Update () {
  
        if(Input.GetMouseButtonDown(0))
        {
            vec_mouseBtnPos = Input.mousePosition;
  
            //将鼠标的位置除以屏幕参数得到范围为0~1的坐标范围
            vec_mouseBtnPos = new Vector2(vec_mouseBtnPos.x / Screen.width, vec_mouseBtnPos.y / Screen.height);
            //设定坐标原点为中点
            vec_mouseBtnPos -= new Vector2(0.5f,0.5f);
            vec_mouseBtnPos *= 2;
            vec_mouseBtnPos.y = -vec_mouseBtnPos.y;
}


2、将点固定在网格交叉点上
看到这里,各位看官你会想到我要做一个什么?哈哈,没错就是一个五子棋,用Shader来写一个五子棋,想想都很激动。好,回归正题,有了上一步的基础,现在我们只需要将鼠标点击的点和附近网格交叉点之间做一个判断,看看是否达到了阈值范围内,如果是,就让这个红色点固定绘制在这个交叉点上。完整的代码如下:
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
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
///
/// 这个脚本实现的点击单个点的效果
///
public class GameControl : MonoBehaviour {
    public Material mat;
    //设置点中的最小误差
    public float clickMinError;
 
    //网格点的坐标集
    private List list_gridIntersectionPos = new List();
 
    //网格点的数量
    private int gridIntersectionNums;
    private float gridSpace;
    private Vector2 vec_mouseBtnPos;
    // Use this for initialization
    void Start()
    {
 
        gridSpace = mat.GetFloat("_tickWidth");
 
        //单个坐标轴上网格点的数量等于横轴坐标间距除以网格间距
        gridIntersectionNums = (int)Mathf.Floor(1.0f / gridSpace); //这里不能只用强制类型转换,如果使用强制类型转换会丢失数据,比如1.0/0.1最后的结果是9
 
        for (int i = -gridIntersectionNums; i <= gridIntersectionNums; i++)
        {
            float x = gridSpace * i;
 
 
            for (int j = -gridIntersectionNums; j <= gridIntersectionNums; j++)
            {
                float y = gridSpace * j;
                list_gridIntersectionPos.Add(new Vector2(x, y));
 
            }
 
        }
    }
     
    // Update is called once per frame
    void Update () {
 
        if(Input.GetMouseButtonDown(0))
        {
            vec_mouseBtnPos = Input.mousePosition;
 
            //将鼠标的位置除以屏幕参数得到范围为0~1的坐标范围
            vec_mouseBtnPos = new Vector2(vec_mouseBtnPos.x / Screen.width, vec_mouseBtnPos.y / Screen.height);
            //设定坐标原点为中点
            vec_mouseBtnPos -= new Vector2(0.5f,0.5f);
            vec_mouseBtnPos *= 2;
            vec_mouseBtnPos.y = -vec_mouseBtnPos.y;
 
          /*  mat.SetFloat("_MouseBtnPosX", vec_mouseBtnPos.x);
            mat.SetFloat("_MouseBtnPosY", vec_mouseBtnPos.y);*/
               //如果点中了网格的交叉点出就显示圆点
               int index = CheckClikedIntersection(vec_mouseBtnPos);
               if (index != -1)
               {
                   //将准确的网格点的位置赋值给vec_mouseBtnPos
                   vec_mouseBtnPos = list_gridIntersectionPos[index];
                   mat.SetFloat("_MouseBtnPosX", vec_mouseBtnPos.x);
                   mat.SetFloat("_MouseBtnPosY", vec_mouseBtnPos.y); 
               }
               Debug.Log("x:" + vec_mouseBtnPos.x + "y:" + vec_mouseBtnPos.y);
 
 
        }
     
    }
    ///
    /// 判断鼠标点中的地方是否在网格的交叉点处
    ///
    ///
    ///
    private int CheckClikedIntersection(Vector2 vec2)
    {
        int clickIndex = -1;
        for (int i = 0; i < list_gridIntersectionPos.Count; i++)
        {
            float errorx = Mathf.Abs(vec2.x- list_gridIntersectionPos[i].x);
            float errory = Mathf.Abs(vec2.y - list_gridIntersectionPos[i].y);
            //如果误差的值小于预设的值则判定点中了
            float error = Mathf.Sqrt(errorx * errorx + errory * errory);
            if(error
这里使用了“CheckClikedIntersection”方法来进行判断和校正,并设置了一个最小误差“clickMinError”。
效果图如图所示:是不是规矩多了,哈哈!
3、当然Shader代码中还要设置接收C#脚本传过来的鼠标点击坐标Uniform类型的变量,即

                                                                "uniform float _MouseBtnPosX;uniform float_MouseBtnPosY;”
完整的Shader代码如下:
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Shader "Unlit/Backgammon"
{
        Properties
        {
                _backgroundColor("面板背景色",Color) = (1.0,1.0,1.0,1.0)
                _axesColor("坐标轴的颜色",Color) = (0.0,0.0,1.0)
                _gridColor("网格的颜色",Color) = (0.5,0.5,0.5)
                _tickWidth("网格的间距",Range(0.1,1)) = 0.1
                _gridWidth("网格的宽度",Range(0.0001,0.01)) = 0.008
                _axesXWidth("x轴的宽度",Range(0.0001,0.01)) = 0.006
                _axesYWidth("y轴的宽度",Range(0.0001,0.01)) = 0.007
                _MouseBtnPosX("鼠标点击的X方向位置",float) = 0.0
                _MouseBtnPosY("鼠标点击的Y方向位置",float) = 0.0
                _MouseBtnRadius("鼠标点中位置圆盘的半径",float) = 0.001
                _MouseDownColor("鼠标点中的颜色",Color) = (1.00, 0.329, 0.298,1.0)
        }
        SubShader
        {
                //去掉遮挡和深度缓冲
                Cull Off
                ZWrite Off
                //开启深度测试
                ZTest Always
  
                CGINCLUDE
                //添加一个计算方法
                float mod(float a,float b)
                {
                        //floor(x)方法是Cg语言内置的方法,返回小于x的最大的整数
                        return a - b*floor(a / b);
                }
  
                //添加第二个计算方法,根据半径,原点和颜色来绘制圆盘       
                fixed3 disk(fixed2 r,fixed2 center,fixed radius,fixed3 color,fixed3 pixel)
                {
                        fixed3 col = pixel;
                        if (length(r - center) < radius)
                        {
                                col = color;
                        }
                        return col;
                }
                ENDCG
  
                Pass
                {
                        CGPROGRAM
                        //敲代码的时候要注意:“CGPROGRAM”和“#pragma...”中的拼写不同,真不知道“pragma”是什么单词
                        #pragma vertex vert
                        #pragma fragment frag
  
                        #include "UnityCG.cginc"
  
                        uniform float4 _backgroundColor;
                        uniform float4 _axesColor;
                        uniform float4 _gridColor;
                        uniform float _tickWidth;
                        uniform float _gridWidth;
                        uniform float _axesXWidth;
                        uniform float _axesYWidth;
  
                        uniform float _MouseBtnPosX;
                        uniform float _MouseBtnPosY;
                        uniform float _MouseBtnRadius;
                        uniform float4 _MouseDownColor;
  
  
                        struct appdata
                        {
                                float4 vertex:POSITION;
                                float2 uv:TEXCOORD0;
                        };
                        struct v2f
                        {
                                float2 uv:TEXCOORD0;
                                float4 vertex:SV_POSITION;
                        };
                        v2f vert(appdata v)
                        {
                                v2f o;
                                o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
                                o.uv = v.uv;
                                return o;
                        }
  
                        fixed4 frag(v2f i) :SV_Target
                        {
                                //将坐标的中心从左下角移动到网格的中心
                                float2 r = 2.0*(i.uv - 0.5);
                                float aspectRatio = _ScreenParams.x / _ScreenParams.y;
                                //r.x *= aspectRatio;
                                fixed3 backgroundColor = _backgroundColor.xyz;
                                fixed3 axesColor = _axesColor.xyz;
                                fixed3 gridColor = _gridColor.xyz;
  
                                fixed3 pixel = backgroundColor;
  
                                //定义网格的的间距
                                const float tickWidth = _tickWidth;
                                if (mod(r.x, tickWidth) < _gridWidth)
                                {
                                        pixel = gridColor;
                                }
                                if (mod(r.y, tickWidth) < _gridWidth)
                                {
                                        pixel = gridColor;
                                }
  
                                //画两个坐标轴
                                if (abs(r.x) < _axesXWidth)
                                {
                                        pixel = axesColor;
                                }
                                if (abs(r.y) < _axesYWidth)
                                {
                                        pixel = axesColor;
                                }
  
  
                                //画一个点
                                pixel = disk(r, fixed2(_MouseBtnPosX, _MouseBtnPosY), _MouseBtnRadius, _MouseDownColor, pixel);
                                return fixed4(pixel, 1.0);
                        }
                        ENDCG
                }
  
        }
  
}
到了这一步还是不能说可以完整的做出一个五子棋,因为现在只能画一个点,当然你可以通过在Shader代码中增加画点的代码来实现画多个点,然而并不能达到我们理想的状况。下一节将会给出一个技巧来实现C#脚本怎么向Shader中传数组来实现Shader代码画多个点。

最后,附上本章的工程下载百度网盘地址
https://pan.baidu.com/s/1jIi9Hoa#list/path=%2F
使用的是Unity 5.3.3版本。
好好学习,天天向上

?

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引