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

原文:http://www.manew.com/thread-96594-1-1.html

一.前言:

上一章中Unity&Shader基础篇-绘图2D图形中学习了怎么画出一个棋盘网格,首先来完善一下这个shader代码。首先,添加属性,属性包括网格的线的宽度,网格的颜色等。完整的代码如下:

Shader "Unlit/Scenes_001_1"
{       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
                }

        }

}

代码中通过一下代码将网格的原点移动到了中心位置

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

得到的效果图如图所示:
Unity&Shader基础篇-绘制网格+圆盘_第1张图片

二、画圆盘

1、原理:在二维坐标系中圆的方程为:(x-a)^2+(y-b)^2=r^2,根据此方程在Shader代码中添加如下的计算方法:

    //添加第二个计算方法,根据半径,原点、原点的偏移量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;
    }

完整代码如下:

Shader "Unlit/Scenes_002"
{
        Properties
        {
                _backgroundColor("面板背景色",Color) = (1.0,1.0,1.0,1.0)
                _col1("圆盘1的颜色_col1",Color) = (0.216, 0.471, 0.698) // blue
                _col2("圆盘2的颜色_col2",Color) = (1.00, 0.329, 0.298) // red
                _col3("圆盘3的颜色_col3",Color) = (0.867, 0.910, 0.247) // yellow

                _center2X("第三个圆盘的原点X的位置_center2X",Range(0.0,1.0)) = 0.9
                _center2Y("第三个圆盘的原点Y的位置_center2Y",Range(0.0,1.0)) = -0.4

                _radius1("第一个圆盘的半径_radius1",Range(0.1,1)) = 0.8
                _radius2("第二个圆盘的半径_radius2", Range(0.1, 1)) = 0.3
                _radius3("第三个圆盘的半径_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,col1
                        pixel= disk(r, fixed2(_center2X, _center2Y), _radius2, col2, pixel);
                        //画第三个圆盘
                        pixel= disk(r, fixed2(-0.8, 0.6), _radius3, col3, pixel);
                        return fixed4(pixel, 1.0);
                }

                ENDCG
        }

        }

}

得到的效果图如图所示:其中红色和蓝色是同心的,也即a和b是一样的都是0,圆的中心在原点位置
Unity&Shader基础篇-绘制网格+圆盘_第2张图片

三、在网格中绘制圆盘

1、根据2中绘制圆盘的原理,只要将a和b通过C#代码传递参数给Shader来控制即可,这个参数就是鼠标点击时候的坐标位置。当然这个位置要经过计算才能应用到网格坐标中,网格坐标的范围为-1~1,通过代码来转换:这个计算方法基本上和Shader代码中的计算方法是一样的,得到的效果图如图:

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;
}

Unity&Shader基础篇-绘制网格+圆盘_第3张图片

2、将点固定在网格交叉点上

看到这里,各位看官你会想到我要做一个什么?哈哈,没错就是一个五子棋,用Shader来写一个五子棋,想想都很激动。好,回归正题,有了上一步的基础,现在我们只需要将鼠标点击的点和附近网格交叉点之间做一个判断,看看是否达到了阈值范围内,如果是,就让这个红色点固定绘制在这个交叉点上。完整的代码如下:

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

[ExecuteInEditMode]
public class Scenes_002 : MonoBehaviour
{

    public Material mat;
    //设置点中的最小误差
    public float clickMinError;

    //网格点的坐标集
    private List list_gridIntersectionPos = new List();

    //网格点的数量
    private int gridIntersectionNums;
    private float gridSpace;
    private Vector2 vec_mouseBtnPos;

    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));

            }

        }
    }

    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);
            Debug.Log(index);
            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 < clickMinError)
            {
                clickIndex = i;
                break;
            }
        }
        return clickIndex;
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        Graphics.Blit(src, dest, mat);
    }
}

这里使用了“CheckClikedIntersection”方法来进行判断和校正,并设置了一个最小误差“clickMinError”,博主测试的误差大概0.08左右。
效果图如图所示:是不是规矩多了,哈哈!
Unity&Shader基础篇-绘制网格+圆盘_第4张图片

3、当然Shader代码中还要设置接收C#脚本传过来的鼠标点击坐标Uniform类型的变量,即:

    uniform float _MouseBtnPosX;
    uniform float _MouseBtnPosY;

完整的Shader代码如下:

Shader "Unlit/Scenes_002_1"
{
        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代码画多个点。

最后,附上本章的工程下载百度网盘地址:
链接:http://pan.baidu.com/s/1c21MXPm 密码:7eik


好了,感谢原作者的分享。

每天进步一点点!!!

你可能感兴趣的:(Unity,Shader)