Unity&Shader基础篇-绘制网格+圆盘
发表于2016-11-24
一、前言
尊重原创,转载请注明出处凯尔八阿哥专栏
上一章点击打开链接中已经画出了一个棋盘网格,首先来完善一下这个画网格的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 } } } |
通过以下代码将网格的轴心移动到中心位置 |
|
得到的效果图如图所示:
二、画一个圆盘
原理:圆的方程为(x-a)^2+(y-b)^2=r^2,根据方程即可以添加如下方法来计算预定圆盘的颜色值信息
|
效果如图所示:
完整的代码如下:
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 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 |
效果图如图所示:是不是规矩多了,哈哈!
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版本。
好好学习,天天向上