Unity的实时绘制与坐标转换总结

转载自:http://mp.weixin.qq.com/s?__biz=MzA4MDc5OTg5MA==&mid=400255987&idx=2&sn=42ddf8e0f520496e4b4df3b165eb97ae&scene=23&srcid=1102Pc1UGIqbCFjEtjeQB4zT#wechat_redirect

最近在搞有关Unity的交互和线路绘制的问题,在这里总结一下。

比如说:我们需要在屏幕上用鼠标拖动,然后屏幕上面会根据我们的动作动态绘制出线路,可以是直线,点,或者抛物线等等。这里主要的问题有两个,第一:是采用什么方法来绘制,第二,采用什么坐标来绘制。

首先我们绘制一根线(或者点集)的话,线的绘制可以采用的方式有:

1.LineRender,

2.NGUI绘制

3.直接使用Unity内置的基本几何图形(cube ,sphere等)做点集合,

4.使用GL这个底层库等等方式。

根据不同的绘制方式,我们需要搞清楚它们所使用的坐标,这里是个很重要的问题,涉及多种坐标的转换问题,Unity本身有这么几种坐标:

1.世界坐标:普通三维世界使用,原点在屏幕中间,比如说:XXobject.transform.position

2.屏幕坐标:鼠标使用(移动平台的手指触控也是),和屏幕分辨率有关,原点在屏幕左下角,如:Input.mousePosition

3.投影坐标:和摄像机投影变换有关,用户使用比较少

4.GUI坐标,Unity内置GUI使用,单位是像素和屏幕坐标一样,但是原点在左上角

下面说说具体使用的方式:

使用LineRenderer :

如果说我们选择绘制线路的方式是LineRender,由于其使用的是世界坐标,假如说我们要实现在屏幕上面点一下,然后就在那个点放置我们的小球。那么我们的步骤大致是:1.捕获鼠标点击的时候的屏幕坐标,2.把屏幕坐标转换为世界坐标。3.把世界坐标赋值给小球。

1. 获取鼠标的屏幕坐标的方法是Vector pos = Input.mousePosition,

2.坐标转换的方法是Camera.main.ScreenToWorldPoint(pos), 但是!,这里有一个非常重要的点要注意,就是由于鼠标的坐标是2d(屏幕坐标),z轴是有问题的,因为屏幕上面的一点对应的的是空间上面的一条射线(如下图,透视投影和正交投影)。

无论是透视投影还是正交投影。所以直接转换过去的世界坐标是很有问题的,z轴不对。经过实践,Unity里面的Camera.main.ScreenToWorld方法,并不会直接能把获取到的屏幕坐标(如:Vector3 A)转成正确的世界坐标。这里我们要先在转成世界坐标之前给它的z轴赋一个有意义的值。如果转换之后再赋值会出错的。你们可以简单的把pos.z = 1;这样赋予正确的值。这里介绍我的做法:由于我知道了我的点的z轴坐标需要和起点一致,所以我先取得起点的世界坐标(如Vector3 B =_vecStart ),转成屏幕坐标Vector C = Camera.main.ScreenToWorldPoint(B),再拿出z轴赋给我们要处理的坐标(A.z= C.z),这样A转换过后的z轴和起点_vecStart一致了。此时A的三个坐标的是有意义的,这时对A进行转换才能达到我们的预期。

坐标的转换才是关键,做完了转换的话,直接赋值给绘制的方法就好了,LineRender的具体使用方法是:

LineRenderer _lineRenderer;

_lineRenderer = gameObject.GetComponent<LineRenderer>();// 需要在控件上挂LineRenderer组件,在这里取得引用

_lineRenderer.SetWidth(0.3f, 0.3f); //设置起点和终点的线框大小

_lineRenderer.SetVertexCount(10); //设置点的个数

_lineRenderer.useWorldSpace = true; // 使用世界坐标

for (int j = 0; j < 10; j++) //指点每个点的坐标,世界坐标,j是点的编号,从零开始

{_lineRenderer.SetPosition(j, new Vector3(0, 0, 0));

}

另外,在inspector面板,还可以可视化的指定材质,点的个数,等等。材质可以设成颜色,这样不太美观(如左下图),设贴图的话在比较段的线还不错,如果设置的点数比较多的话,采用贴图很有问题,贴图被拉伸。如果要绘制长线的话只能采取多个LineRenderer,首尾连接,这样又会变得非常的麻烦。

使用NGUI :

采取NGUI这个比较成熟的插件的话,绘制点线的话,图元的制作会变得非常的简单,主要问题是坐标的问题。由于NGUI采用的是却别于Unity的几大坐标,使用自己独有的一套坐标,因此需要在进行一次转换,至于Unity和NGUI的对接点在屏幕坐标,因为NGUI的几个转换坐标的方法中ScreenToWorldPoint(vecTempScreen)这个方法很像Unity 的那个,这里转换后的世界坐标是NGUI的世界坐标,转换前的屏幕坐标就是Unity的屏幕坐标,所以可以用屏幕坐标变中介。

好了,具体的做法是这样子的,如果是Unity的场景中一个物体Cube,要把它的世界坐标转成NGUI坐标,我们可以这么做:

Vector3 pos = Cube.transform.position;
Vector3 posScreen = Camera.main.WorldToScreenPoint(pos); //这里的Camera.main指的是世界坐标场景中的摄像机

Vector posNgui = UICamera.currentCamera.ScreenToWorldPoint(posScreen); //这里的currentCamera指的是NGUI场景中的摄像机,一般在UIRoot的子节点下

左下图为上面代码中的摄像机参数的可视化显示,中下图为运行效果,右下图为NGUI的坐标和世界坐标的对比(白色为世界坐标,红框里面为NGUI内容)

这里要提一点,有时候Unity会提示 UICamera.currentCamera取不到实例,这个极有可能是场景中有好几台摄像机(最基本的世界场景一台,NGUI一台,背景和背包各用一台摄像机是很正常的,所以场景中的摄像机比较多的情况下,UICamera.currentCamera取不到我们想要的那台摄像机,所以自己直接找到那台拖拽赋值是最保险的),我们可以自己定义一个变量public Camera NguiCurrentCamera,直接在Inspector界面中把UIRoot中的摄像机拖给它,这样不会为空了。这么使用就可以NguiCurrentCamera.ScreenToWorldPoint(posScreen);接下来,就可以直接把posNgui赋值给NGUI的图元的坐标了,比如说:NGUIGameObject.transform.position = posNgui.

注意:

如果是直接获取屏幕坐标(比如说鼠标点击),转为NGUI世界坐标,我们很容易想到直接把上面的步骤拿来用,忽略第一步即可,思路是这样的。但是不要忘记,还是那个问题,屏幕坐标的Z轴是有问题的,所以应该先把Z轴赋予正确的值,比如说1,再进行转换:

Vector3 posScreen = Input.mousePosition;

posScreen.z = 1;

Vector posNgui= UICamera.currentCamera.ScreenToWorldPoint(posScreen);

这样posNgui就可以使用了。

另外提一点,由于世界坐标相对来说比较小,不像屏幕那样1360*768这种,可能一个场景中的边界的距离就是30-60左右,所以计算的时候要注意误差问题。

使用GL:

GL是Unity里面的一个底层的图形库,其实是GL立即绘制函数 只用当前material的设置。因此除非你显示指定mat,否则mat可以是任何材质。并且GL可能会改变材质。

GL是立即执行的,如果你在Update()里调用,它们将在相机渲染前执行,相机渲染将会清空屏幕,GL效果将无法看到。通常GL用法是,在camera上贴脚本,并在OnPostRender()里执行。

注意:

1.GL的线等基本图元并没有uv. 所有是没有贴图纹理影射的,shader里仅仅做的是单色计算或者对之前的影像加以处理。所以GL的图形不怎么美观,如果需要炫丽效果不推荐

2.GL所使用的shader里必须有Cull off指令,否则显示会变成如下

使用GL的一般步骤是:

这是官方的一个例子,小作修改

public Vector3[] _GLPos; //我们要绘制的点
public IsDraw3D = true; //3d或者2d绘制
static Material lineMaterial;

static void CreateLineMaterial()

{

if (!lineMaterial)

{

lineMaterial = new Material("Shader \"Lines/Colored Blended\" {" +

"SubShader { Pass { " +

" Blend SrcAlpha OneMinusSrcAlpha " +

" ZWrite Off Cull Off Fog { Mode Off } " +

" BindChannels {" +

" Bind \"vertex\", vertex Bind \"color\", color }" +

"} } }");

lineMaterial.hideFlags = HideFlags.HideAndDontSave;

lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave;

}

}

void OnPostRender()

{

float axisZ = Camera.main.transform.position.z;

CreateLineMaterial();

lineMaterial.SetPass(0);

if (!IsDraw3D)

{

GL.LoadOrtho();

}

GL.Begin(GL.LINES);

GL.Color(new Color(1, 1, 1));

for (int i = 0; i < _GLPos.Length; i++)

{

GL.Vertex(_GLPos[i]);

}

GL.End();

}

这其实是官方文档的一个例子,我进行了一些小小的修改,上面是部分摘录,脚本挂在摄像机中,在OnPostRender中的代码有些可能看起来很熟悉,没错,在window编程中我们绘制的时候也是类似于这种写法,底层使用的是状态机,设置各种绘制属性(java,windows,or mfc甚至是directx,设置画刷颜色,线框等),在下一次改变之前都是使用前一次设置的属性。其实使用的方法不难,但是这里有一个关键点:

GL.LoadOrtho();

这个方法的意思是使用设置ortho perspective,即水平视角。范围(0,0,-1) to (1,1,100). (还是坐标问题)主要用于在纯2D里绘制图元。GL.Vertex3()的取值范围从左下角的(0,0,0) 至右上角的(1,1,0),所以我们如果设置了这个东西,那么我们的代码要这么写:

假如我们要使用vecTemp这个世界坐标赋值给

坐标转换就不多说了。世界坐标转屏幕坐标,在缩放到0-1的范围

Vector3 posGL = Camera.main.WorldToScreenPoint(vecTemp);

posGL = new Vector3(posGL.x / Screen.width, posGL.y / Screen.height, 0);

_GLPos[i] = posGL;

我们要把坐标限制在范围(0,0,-1) to (1,1,100)内就需要进行相应的缩放,很明显,vecTemp是屏幕坐标了

如果没有调用 GL.LoadOrtho();那就是使用普通世界坐标了

_GLPos[i] = new Vector3(vecTemp.x, vecTemp.y, 0);

下面来看看,是否使用GL.LoadOrtho();的不同效果,可以看出感觉是一样的,但是坐标其实不一样,图1 数值x,y轴的范围始终在0-1之间,图2,会比1

图一

图二

使用Unity的内置几何体

在绘制点线的时候,如果可以的话,其实可以直接使用Unity的内置几何体,如Cube,Sphere这些直接拿来用,坐标使用的就是普通的世界坐标,比LineRenderer有时候更加简单方便。可以使用如Sphere制作一个自己满意的点,做成预制体,动态加载进场景中,数量我们根据自己的需求定。然后根据我们的具体情况设置世界坐标即可。


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值