Unity Shader 用鼠标绘制自由多边形

实现 : 支持在 Plane 上用鼠标点击,确定多边形顶点,并且绘制多边形的边,在内部填充颜色 ;

Plane 带有碰撞体 , 使用鼠标选取位置的时候涉及到碰撞检测 .

ScriptShader005.cs 脚本实现鼠标点击和向 Shader 传递信息的功能 .
Shader005.shader 实现多边形的绘制功能 . 传送门 → 绘制多边形的函数

效果图 :

MainCamera 关联的脚本 ScriptShader005.cs :

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class ScriptShader005 : MonoBehaviour
{
	// 绑定材质
	public Material mat;

	// 存储获取的 3D 坐标
	Vector3[] worldPos;

	// 存储待绘制的多边形顶点屏幕坐标
	Vector4[] screenPos;

	// 多边形顶点总数
	int maxPointNum = 10;

	// 当前已经获得的顶点数
	int currentPointNum = 0;

	// 传递顶点数量给 Shader
	int pointNum2Shader = 0;

	// 是否处于顶点获取过程
	bool InSelection = true;

	void Start ()
	{
		worldPos = new Vector3[maxPointNum];
		screenPos = new Vector4[maxPointNum];
	}

	void Update ()
	{
		// 传递顶点屏幕位置信息给 shader
		mat.SetVectorArray ("Value", screenPos);

		// 传递顶点数量给 shader
		mat.SetInt ("PointNum", pointNum2Shader);

		// 使用摄像机发射一条射线 , 以获取要选择的 3D 位置
		Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
		RaycastHit hit;
		if (Physics.Raycast (ray, out hit, 100)) {
			Debug.DrawLine (ray.origin, hit.point, Color.red);
		}

		// 利用鼠标点击来获取位置信息
		if (Input.GetMouseButtonDown (0) && InSelection) {
			if (currentPointNum < maxPointNum) {
				currentPointNum++;
				pointNum2Shader++;
				worldPos [currentPointNum - 1] = hit.point;
				Vector3 v3 = Camera.main.WorldToScreenPoint (worldPos [currentPointNum - 1]);
				screenPos [currentPointNum - 1] = new Vector4 (v3.x, v3.y, v3.z, 0);
			} else {
				// 超过了多边形顶点总数就不能继续获取
				InSelection = false;
			}
		}

		// 实时更新已选择的 3D 点的屏幕位置
		for (int i = 0; i < maxPointNum; i++) {
			Vector3 v3 = Camera.main.WorldToScreenPoint (worldPos [i]);
			screenPos [i] = new Vector4 (v3.x, v3.y, v3.z, 0);
		}

		// 检测是否有 3D 点移动到了摄像机后面 , 如果有 , 则停止绘制
		for (int i = 0; i < currentPointNum; i++) {
			if (Vector3.Dot (worldPos [i] - Camera.main.transform.position, Camera.main.transform.forward) <= 0) {
				pointNum2Shader = 0;
				break;
			}
			pointNum2Shader = currentPointNum;
		}

	}

	// 抓取当前的渲染图像进行处理
	void OnRenderImage (RenderTexture src, RenderTexture dest)
	{
		Graphics.Blit (src, dest, mat);
	}
}

Shader005.shader :

Shader "Custom/Shader005" {

	Properties {
		// 定义基本属性 , 可以从编辑器中进行设置的变量

	}

	CGINCLUDE

	// 从应用程序传入顶点函数的数据结构定义
	struct a2v {
		float4 vertex : POSITION;
		float2 uv : TEXCOORD0;
	};

	// 从顶点函数传入片元函数的数据结构定义
	struct v2f {
		float2 uv : TEXCOORD0;
		float4 vertex : SV_POSITION;
	};

	// 定义贴图变量
	sampler2D _MainTex;

	// 定义与脚本进行通信的变量 , 10 个顶点
	vector Value[10];

	int PointNum = 0;


	// 计算两点之间的距离的函数
	float Dis(float4 v1, float4 v2) {
		return sqrt(pow(v1.x - v2.x,2) + pow(v1.y - v2.y,2));
	}

	// 绘制线段
	bool DrawLineSegment(float4 p1, float4 p2, float lineWidth, v2f i) {
		float4 center = float4((p1.x + p2.x)/2, (p1.y + p2.y)/2, 0, 0);
		// 计算点到直线的距离
		float d = abs((p2.y - p1.y) * i.vertex.x +
			(p1.x - p2.x) * i.vertex.y +
			p2.x * p1.y - p2.y * p1.x) / sqrt(pow(p2.y - p1.y, 2) + pow(p1.x - p2.x, 2));
		// 小于或者等于线宽的一般的时候就属于直线的范围
		float lineLength = sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
		if(d <= lineWidth/2 && Dis(i.vertex, center) < lineLength/2) {
			return true;
		}
		return false;
	}

	// 绘制多边形
	bool pnpoly(int nvert, float4 vert[10], float testx, float testy) {
		int i, j;
		bool c = false;
		float vertx[10];
		float verty[10];

		for(int n=0; n<nvert; n++) {
			vertx[n] = vert[n].x;
			verty[n] = vert[n].y;
		}

		for(i=0,j=nvert-1; i<nvert; j=i++){
			if(((verty[i] > testy) != (verty[j] > testy)) && (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i])) {
				c = !c;
			}
		}
		return c;
	}

	v2f vert (a2v v) {
		v2f o;
		// 将物体顶点从模型空间转换到摄像机裁剪空间
		// 简写方式 : o.vertex = UnityObjectToClipPos(v.vertex);
		o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
		// 2D UV 坐标变换 , 简写方式 : o.uv = TRANSFORM_TEX(v.uv, _MainTex);
		// o.uv = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw
		return o;
	}

	fixed4 frag(v2f i) : SV_Target {
		// 绘制多边形顶点
		for(int j=0; j< PointNum; j++) {
			if(Dis(i.vertex, Value[j]) < 10/2) {
				return fixed4(1,0,0,0.5);
			}
		}

		// 绘制多边形的边
		for(int k=0; k<PointNum; k++) {
			if(k == PointNum - 1) {
				if(DrawLineSegment(Value[k], Value[0], 2,i)) {
					return fixed4(1, 1, 0, 0.5);
				}
			} else {
				if(DrawLineSegment(Value[k], Value[k+1], 2, i)) {
					return fixed4(1, 1, 0, 0.5);
				}
			}
		}

		// 填充多边形的内部
		if(pnpoly(PointNum, Value, i.vertex.x, i.vertex.y)) {
			return fixed4(0, 1, 0, 0.3);
		}
		return fixed4(0, 0, 0, 0);
	}

	ENDCG

	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200

		Pass {
			// 选取 Alpha 混合方式
			Blend SrcAlpha OneMinusSrcAlpha
			// 在 CGPROGRAM 代码块中写自己的处理过程
			CGPROGRAM
			// 定义顶点函数和片元函数的入口分别为 vert 和 frag
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			ENDCG
		}
	}
}

点到直线距离公式 :

参考 : http://blog.csdn.net/zzlyw/article/details/53992048

End.

→ 源码 GitHub

posted @ 2017-03-10 18:32  Hu&Fei  阅读(1542)  评论(0编辑  收藏  举报