前言

在前面的文章中,我们已经了解了怎样使用Unity Shader来绘制简单的点和线,本文将延续上次的话题,讲述一下如何在场景中使用Unity Shader绘制自由多边形。

本文所述的程序,支持在地图中用鼠标点击,确定多边形顶点,并且绘制多边形的边,在内部填充半透明的颜色。先展示一下最终效果。完整工程下载地址在本文末尾处。

1 开发工具介绍

Windows 10(64位)

Unity 5.4.1(64位)

2 建立工程

首先建立一个新工程,命名为Polygon,并创建一个Scene。在场景中新建一个Plane,该Plane是默认带有碰撞体的,这个碰撞体必须有,因为我们在后边使用鼠标选取位置的时候,涉及到碰撞检测。给该Plane加上贴图。

3 核心代码实现

3.1  Polygon.cs脚本中实现的是鼠标点击和向shader传递信息的功能

(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; //传递顶点数量给shaderbool InSelection=true; //是否处于顶点获取过程void Start () {worldPos = new Vector3[maxPointNum];screenPos = new Vector4[maxPointNum];}void Update () {mat.SetVectorArray("Value",screenPos); //传递顶点屏幕位置信息给shadermat.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;  }  ENDCGSubShader  {  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

【Unity Shader学习笔记】(五)使用鼠标绘制自由多边形(附完整工程源码)相关推荐

  1. Unity Shader 学习笔记(27)渲染轮廓线(描边)方法、卡通风格渲染、素描风格渲染

    Unity Shader 学习笔记(27)渲染轮廓线(描边)方法.卡通风格渲染.素描风格渲染 参考书籍:<Unity Shader 入门精要> 渲染轮廓线(描边) 五种方法: 基于观察角度 ...

  2. Unity Shader 学习笔记(33) 全局光照(GI)、反射探针、线性空间和伽马空间、高动态范围(HDR)

    Unity Shader 学习笔记(33) 全局光照(GI).反射探针.线性空间和伽马空间.高动态范围(HDR) 参考书籍:<Unity Shader 入门精要> [<Real-Ti ...

  3. Unity Shader 学习笔记(3)URP渲染管线带阴影PBR-Shader模板(ASE优化版本)

    此 Shader 已经不是最新版本,最新版本见本专栏的第四篇文章: Unity Shader 学习笔记(4) 材质面板截图: 功能实现(URP渲染管线下): PBR材质.投射和接收阴影. 代码展示: ...

  4. 【Unity】Unity Shader学习笔记(二)渲染管线

    文章目录 渲染管线(Randering Pipeline) 渲染流程 可编程渲染管线 应用阶段 把数据加载到显存中 设置渲染状态 调用DrawCall 几何阶段.光栅化阶段 渲染管线(Randerin ...

  5. Unity Shader学习笔记(5)基于摄像机深度和法线的后处理描边效果

    文章目标 : 主要参考书籍为<Unity Shader入门精要>,本文主要注重于整理,方便后续直接调用. 渲染效果图: 主要相关代码: 摄像机脚本文件: using System.Coll ...

  6. Unity Shader学习笔记/Urp/水墨风效果

    实现简易的水墨风效果大致分为三部分: 1.将原始的rgb贴图转化为灰度图 float4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN ...

  7. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板

    写在之前 Shader变体.Shader属性定义技巧.自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用 ...

  8. 【Unity Shader学习笔记】实现反射与折射模拟水面、使用grabPass与环境贴图

    文章目录 写在前面 一个水波效果 大致组成部分与对应的实现方案 交界线与深度贴图 折射效果与GrabPass 使用Cubemap与法线信息来模拟反射 在正确的地点创建对应的cubemap 通过贴图获取 ...

  9. Unity Shader 学习笔记(4)URP渲染管线带阴影PBR-Shader模板 -- 新增可自定义阴影颜色

    材质面板截图 功能实现(URP渲染管线下): 1.进一步优化Shader结构和算法: 2.包含PBR材质: 3.投射和接收阴影,并升级支持自定义阴影颜色: 4.支持点光源照射(但不支持点光源阴影). ...

最新文章

  1. Vim - 视图模式
  2. Linux驱动修炼之道-内存映射
  3. 从一则笑话里分析项目需求的缺陷
  4. Linux编程手册读书笔记第五章(20140408)
  5. Ubuntu下安装Chrome浏览器的两个方法
  6. 使用Eclipse调试Android Native Application---cocos2d-x + Eclipse + Android + ndk
  7. 报名截止仅剩5天!50万冠军大奖,错过再等一年!
  8. A Byte of Python 笔记(2)基本概念:数、字符串、转义符、变量、标识符命名、数据类型、对象...
  9. 机器学习(一)绪论、算法总结
  10. winform 添加listview数据
  11. 两块stm32仿真protues串口通信程序
  12. android 弱网测试工具,app弱网测试及使用的工具
  13. 013.自驾游加油方案
  14. 基于阿里云的应用系统三级等保1.0测评总结
  15. Python的简单代码:两天肝出画函数图像(散点图)的程序(不用matplotlib)(含白菜也能看懂的超超超详细讲解和源代码哦)
  16. MFC 控件类型和状态
  17. 解决微信小程序主包太大问题
  18. java compiler类_利用 JavaCompiler 编译 Java 类文件
  19. DDoS攻击解决方案-云防护
  20. 算法工程师面试之OOV问题如何解决?

热门文章

  1. 【nlp学习】中文命名实体识别(待补充)
  2. Arduino使用雨滴模块
  3. 讯飞智能录音笔SR302为职场人带来办公新体验
  4. 新能源汽车数据/新能源汽车销售数据/进出口数据
  5. 能定位的不仅GPS,还有它!
  6. 助特朗普胜选、英国脱欧,深扒FB丑闻背后的神秘数据公司如何玩转人心
  7. 基于Acgis从全球.nc数据中提取中国地图并计算地区CO2值
  8. 怎么把电脑文件传到弹性云服务器,怎么把电脑文件传到弹性云服务器
  9. 开源项目贡献者_如何管理开源项目的临时贡献者
  10. 斗鱼直播Android开发二面被刷,赶紧收藏!