【Unity Shader學習筆記】(五)使用鼠標繪製自由多邊形(附完整工程源碼)

前言css

在前面的文章中,咱們已經瞭解了怎樣使用Unity Shader來繪製簡單的點和線,本文將延續上次的話題,講述一下如何在場景中使用Unity Shader繪製自由多邊形。html

本文所述的程序,支持在地圖中用鼠標點擊,肯定多邊形頂點,而且繪製多邊形的邊,在內部填充半透明的顏色。先展現一下最終效果。完整工程下載地址在本文末尾處。markdown


1 開發工具介紹數據結構

Windows 10(64位)app

Unity 5.4.1(64位)編輯器

 

2 創建工程函數

首先創建一個新工程,命名爲Polygon,並建立一個Scene。在場景中新建一個Plane,該Plane是默認帶有碰撞體的,這個碰撞體必須有,由於咱們在後邊使用鼠標選取位置的時候,涉及到碰撞檢測。給該Plane加上貼圖。工具


3 核心代碼實現開發工具

3.1  Polygon.cs腳本中實現的是鼠標點擊和向shader傳遞信息的功能spa

(1)爲了實現鼠標點選場景中的3D位置,須要使用射線

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
RaycastHit hit;  
if (Physics.Raycast(ray, out hit, 100))  
{  
	Debug.DrawLine(ray.origin, hit.point);  					 
}  

(2)向shader傳遞頂點的位置和數量

mat.SetVectorArray("Value",screenPos); //傳遞頂點屏幕位置信息給shader
mat.SetInt ("PointNum",pointNum2Shader); //傳遞頂點數量給shader

(3)將鼠標點擊的位置轉化爲屏幕座標

worldPos[currentpointNum-1] = hit.point;
Vector3 v3 = Camera.main.WorldToScreenPoint (worldPos [currentpointNum-1]);
screenPos[currentpointNum-1] = new Vector4(v3.x,Screen.height-v3.y,v3.z,0);

3.2 Polygon.shader中實現多邊形的繪製功能

(1)計算兩點之間的距離函數

float Dis(float4 v1,float4 v2)
{
	return sqrt(pow((v1.x-v2.x),2)+pow((v1.y-v2.y),2));
}  

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

(3)繪製多邊形的函數

參考:https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

bool pnpoly(int nvert, float4 vert[6], float testx, float testy)
{
    
    int i, j;
    bool c=false;
    float vertx[6];
    float verty[6];

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

4 完整的C#腳本和Shader代碼

Ploygon.cs

using UnityEngine;
using System.Collections;
[ExecuteInEditMode] 
public class Polygon : MonoBehaviour {

	public Material mat; //綁定材質
	Vector3[] worldPos; //存儲獲取的3D座標
	Vector4[] screenPos; //存儲待繪製的多邊形頂點屏幕座標
	int maxPointNum=6;  //多邊形頂點總數
	int currentpointNum =0; //當前已經得到的頂點數
	int pointNum2Shader =0; //傳遞頂點數量給shader
	bool InSelection=true; //是否處於頂點獲取過程

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

	void Update () {
		mat.SetVectorArray("Value",screenPos); //傳遞頂點屏幕位置信息給shader
		mat.SetInt ("PointNum",pointNum2Shader); //傳遞頂點數量給shader

		//使用攝像機發射一條射線,以獲取要選擇的3D位置
		Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
		RaycastHit hit;  
		if (Physics.Raycast(ray, out hit, 100))  
		{  
			Debug.DrawLine(ray.origin, hit.point);  					 
		}  

		//利用鼠標點擊來獲取位置信息
		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,Screen.height-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,Screen.height-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);  
	}  		
}


Polygon.shader

Shader "Unlit/polygon"
{
	Properties  
    {  
        //定義基本屬性,能夠從編輯器裏面進行設置的變量  
        // _MainTex ("Texture", 2D) = "white" {}  
    }  

	CGINCLUDE
			//從應用程序傳入頂點函數的數據結構定義 
	 		struct appdata  
	        {  
	            float4 vertex : POSITION;  
	            float2 uv : TEXCOORD0;  
	        };  
	        //從頂點函數傳入片斷函數的數據結構定義  
	        struct v2f  
	        {  
	            float2 uv : TEXCOORD0;  
	            float4 vertex : SV_POSITION;  
	        };  
	        //定義貼圖變量  
	        sampler2D _MainTex;  
	        // float4 _MainTex_ST;  

	        //定義與腳本進行通訊的變量
	        vector Value[6]; 
	        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;
	        }

	        //繪製多邊形,這裏限制了頂點數不超過6。能夠本身根據須要更改。
	        bool pnpoly(int nvert, float4 vert[6], float testx, float testy)
	        {
	            
	            int i, j;
	            bool c=false;
	            float vertx[6];
	            float verty[6];

	            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 (appdata 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])<3)
	                {
	                    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);
	            //fixed4 col = tex2D(_MainTex, i.uv); 
	            //return col;  
	        }  
	ENDCG

    SubShader  
    {  
        Tags { "RenderType"="Opaque" }  
        LOD 100  
        Pass  
        {  
            //選取Alpha混合方式  
            Blend  SrcAlpha OneMinusSrcAlpha  
            //在CGPROGRAM代碼塊中寫本身的處理過程  
            CGPROGRAM  
            //定義頂點函數和片斷函數的入口分別爲vert和frag  
            #pragma vertex vert  
            #pragma fragment frag  
            //包含基本的文件,裏面有一些宏定義和基本函數  
            #include "UnityCG.cginc"               
           
            ENDCG  
        }  
    }  
}

5 運行效果





小結

本文介紹的是關於Unity Shader的一種基本應用。使用了簡單的繪製技術,完成了在場景中進行自由多邊形區域的選擇功能。目前,還只是一種簡單的實現,僅僅展現了繪製一個多邊形。讀者能夠根據本身的須要,擴展其相關功能。


---------------------------------------------------------------------------------------------------------------------------------

工程文件打包下載:

連接: https://pan.baidu.com/s/1nvRSweH 密碼: jr2g

相關文章
相關標籤/搜索