unity3d 图形库的使用教程7.5 GL图象库详细使用说明教程
    GL图象库是底层的图象库,主要功能是使用程序来绘制常见的2D与3D几何图形。这些图形具有一定的特殊性,他们不属于3D网格图形,
只会以面的形式渲染。使用GL图象库,可在屏幕中绘制2D几何图形,并且该几何图形将永远显示在屏幕当中,不会因为摄象机的移动而
改变。2D图形的呈现方式和前面章节介绍的GUI有点类似,值得注意的是,绘制2D图像时,需要使用GL.LoadOrtho()方法将图形映射在
平面中;如果绘制的是3D图形,就无须使用此方法。
    使用GL图象库时,需要将所有绘制相关的内容写在OnPostRender()方法中。此方法由系统自身调用,无法手动调用。此外,有关GL图象
库的脚本需要绑定在Hierarchy视图中的摄象机对象当中,否则将无法显示绘制的图形
绘制线
    在了解如何绘制线之前,先熟悉Unity中GL图象库的平面坐标系。按照箭头所指的方向,平面坐标系的原点(0,0)位于左下脚。
    值得注意的是,GL图象库的平面坐标和普通坐标是有区别的,GL图象库的x轴的最大值是1,y轴的最大值也为1,而不是按照像素
来计算的,因此,在GL图象库的平面坐标系中,每个点的横坐标和纵坐标都应当是0与1之间的浮点数,而真实的像素坐标需要根据这
个浮点数来计算。
    比如当前游戏屏幕的像素宽高是500*500,在GL图象库平面上选择一个点(0.5f,0.5f),那么这个点的真实像素的横坐标和纵坐标应
当是:500(屏幕宽)*0.5(x坐标) = 250
      500(屏幕高)*0.5(x坐标) = 250
public class Script:MonoBehaviour
{
//绘制线段材质
public Material material;
//此绘制方法由系统调用
void OnPostRender()
{
if(!material)
{
Debug.LogError(“请给材质资源赋值”);
Return;
}
//设置该材质通道,0为默认值
Material.SetPass(0);
GL.LoadOrtho();
//表示开始绘制,绘制类型为线段
GL.Begin(GL.LINES);
//绘制线段
DrawLine(0,0,200,100);
DrawLine(0,50,200,100);
DrawLine(0,100,200,200);
GL.End();
}
void DrawLine(float x1,float y1;float x2,float y2)
{
//绘制线段,需要将屏幕中某个点的像素坐标点除以屏幕完成宽或高
GL.Vetex(new Vector3(x1/Screen.width,y1/Screen.height,0));
GL.Vetex(new Vector3(x2/Screen.width,y2/Screen.height,0));
}

7.5.2实例----绘制曲线(228)
本例通过GL图象库记录鼠标移动的轨迹并且将其以曲线的形式显示在屏幕当中,如图所示,具体实现原理是:记录鼠标在Game视图中移动
时每一点的坐标,然后将鼠标移动的坐标存储在链表中,使用绘制方法OnPostRender()遍历链表中记录的鼠标坐标点,最后通过GL图象库
绘制线段的方法将这些点两两连成一条线段
当前鼠标x轴位置:835
当前鼠标y轴位置:894
public class Script:MonoBehaviour
{
//绘制线段材质
public Material material;
Private List<Vector> lineInfo;
void Start()
{
//初始化鼠标线段链表
lineInfo = new List<Vector3>();
}
void Update()
{
//将每次鼠标改变的位置存储进链表
lineInfo.Add(Input.mousePosition);
}
void OnGUI()
{
GUILayout.Label(“当前鼠标x轴位置:” +Input.mousePosition.x)
GUILayout.Label(“当前鼠标y轴位置:”+Input.mousePosition.y)
}
//此绘制方法又系统调用
void OnPostRender(){
if(!material)
{
Debug.LogError(“请给材质资源赋值”);
Return;
}
//设置该材质通道,0为默认值
material.SetPass(0);
//设置绘制2D图象
GL.LoadOrtho();
//表示开始绘制,绘制类型为线段
GL.Begin(GL.LINES);
//得到鼠标信息的总数量
int size=lineInfo.Count;
//遍历鼠标点的链表

for(int i=0;i<size-1;i++)
{
Vector3 start = lineInfo[i];
Vector3 end = lineInfo[i+1];
//绘制线段
DrawLine(start.x,start.y,end.x,end.y);
//结束绘制
GL.End();
}
void DrawLine(float x1,float y1;float x2,float y2)
{
//绘制线段,需要将屏幕中某个点的像素坐标点除以屏幕完成宽或高
GL.Vetex(new Vector(x1/Screen.width,y1/Screen.height,0));
GL.Vetex(new Vector(x2/Screen.width,y2/Screen.height,0));
}
}
在上述代码中,我们通过Update()方法获取当前鼠标的位置,将每帧的鼠标位置存储在lineInfo链表中,然后在OnPostRender()中遍历
这个链表,将链表中记录的鼠标坐标点连接起来绘制在屏幕当中。
绘制四边形
在平面内,由不在同一条直线的四条线段首尾顺序相接组成的图形就是四边形。要确定平面中的一个四边形,就需要知道4个点,然后将
这4个点连接起来即可。在GL中绘制四边形,需要使用GL.Begin(GL.QUADS)方法,该方法中参数表示需要绘制的图形为四边形。如果设置
的4个点在一条直线上,或者只设置了其中3个点,或者两个点重叠,无法让这4个点构成一个四边形,程序就无法绘制该图形,这里需要
读者注意.
本例共绘制了三组几何图形------两个正四边形和一个无规则四边形
public class Script:MonoBehaviour
{
public Material mat0;
public Material mat1;
public Material mat3;
void OnPostRender()
{
//绘制正四边形
DrawRect(100,100,100,100,mat0);
DrawRect(250,100,100,100,mat1);
//绘制无规则四边形
DrawQiads(15,5,10,115,95,110,90,10,mat3);
}
/**
绘制正四边形
float x:x轴起始坐标
float y:y轴起始坐标
float width:正四边形的宽
float height:正四边形的高
*/
void DrawRect(float x,float y,float width,float height,Material mat)
{
GL.PushMatrix();
mat.SetPass(0);
GL.LoadOrtho();
//绘制类型为四边形
GL.Begin(GL.QUADS);
GL.Vertex3(x/Screen.width,y/Screen.height,0);
GL.Vertex3(x/Screen.width,(y+height)/Screen.height,0);
GL.Vertex3(x+width)/Screen.width,(y+height)/Screen.height,0);

GL.End();
GL.PopMatrix();
}

/**
绘制无规则的四边形
float x1:起始点1的横坐标
float y1:起始点1的横坐标
float x2:起始点2的横坐标
float y2:起始点2的横坐标
float x3:起始点3的横坐标
float y3:起始点3的横坐标
float x4:起始点4的横坐标
float y4:起始点4的横坐标
void DrawQuads(float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4,Material mat)
{ GL.PushMatrix();
mat.SetPass(0);
GL.LoadOrtho();
//绘制类型为四边形
GL.Begin(GL.QUADS);
GL.Vertex3(x1/Screen.width,y1/Screen.height,0);
GL.Vertex3(x2/Screen.width,y2/Screen.height,0);
GL.Vertex3(x3/Screen.width,y3/Screen.height,0);
GL.Vertex3(x4/Screen.width,y4/Screen.height,0);
GL.End();
GL.PopMatrix();
}}
为了说明正四边形和无规则四边形之间的区别,本例将它们封装成两个不同的方法,其中DrawRect()方法用于绘制无规则四边形。
在上述代码的最后,我们使用GL.End()方法绘制的四边形显示在屏幕中。

7.5.4绘制三角形
绘制三角形之前,需要确定平面中的3个点,并且保证这3个点能构成一个三角形,然后将3个点首尾连接起来即可。绘制三角形时,
可以使用GL.Begin(GL.TRIANGLE)方法,该方法的参数为三角形的类型.本例在屏幕中央绘制了一个正三角形,具体代码如代码清单
public class Script:MonoBehaviour
{
//材质
public Material mat;
void OnPostRender()
{
//绘制三角形
DrawTriangle(100,0,100,200,200,100,mat);
}
void DrawTriangle(float x1,float y1,float x2,float y2,float x3,float y3,Material mat)
{
mat.SetPass(0);
GL.LoadOrtho();
//绘制三角形
GL.Begin(GL.TRAINGLES);
GL.Vertex3(x1/Screen.width,y1/Screen.height,0);
GL.Vertex3(x2/Screen.width,y2/Screen.height,0);
GL.Vertex3(x3/Screen.width,y3/Screen.height,0);
GL.End();
}}
在上述代码中,我们使用GL.Vertex3()方法确定三角形三个顶点的位置,并将绘制三角形的所有方法封装在
DrawTriangle()方法中,最后使用GL.End()方法将三角形显示在屏幕中。需要说明的是,在调用DrawTriangle()方法时,
需要将三个点的坐标与材质传入该方法。
绘制3D几何图形
GL图形库不仅支持绘制2D几何图形,还支持绘制3D几何图形,而本例将在3D世界中绘制三个平面四边形,如图7-17所示,
为了让读者更方便看出立体效果,我们在Game视图中添加了一个立方体组件作为视图的参照物。通过随时移动鼠标来修改摄像机朝向
的位置,可以观察它们之间的区别。圆圈内就是使用GL绘制的图形,它会随着摄像机的位置改变而发生移动,
具体的代码如代码清单7-19所示


                     图7-17    立方图形
代码7-19
public class Script:MonoBehaviour
{
public Material mat0;
public Material mat1;
public Material mat3;
void OnPostRender()
{
//绘制正四边形
DrawRect(100,100,100,100,mat0);
DrawRect(250,100,100,100,mat1);
//绘制无规则四边形
DrawQuads(15,5,10,115,95,110,90,10,mat3);
}
/**
绘制正四边形
float x:x轴起始坐标
float y:y轴起始坐标
float width:正四边形的宽
float height:正四边形的高
*/
void DrawRect(float x,float y,float width,float height,Material mat)
{
GL.PushMatrix();
mat.SetPass(0);
//绘制类型为四边形
GL.Begin(GL.QUADS);
GL.Vertex3(x/Screen.width,y/Screen.height,0);
GL.Vertex3(x/Screen.width,(y+height)/Screen.height,0);
GL.Vertex3(x+width)/Screen.width,(y+height)/Screen.height,0);
GL.Vertex3(x+width)/Screen.width, y/Screen.height,0);

GL.End();
GL.PopMatrix();
}

/**
绘制无规则的四边形
float x1:起始点1的横坐标
float y1:起始点1的横坐标
float x2:起始点2的横坐标
float y2:起始点2的横坐标
float x3:起始点3的横坐标
float y3:起始点3的横坐标
float x4:起始点4的横坐标
float y4:起始点4的横坐标
void DrawQuads(float x1,float y1,float x2,float y2,float x3,float y3,float x4,float y4,Material mat)
{ GL.PushMatrix();
mat.SetPass(0);
//绘制类型为四边形
GL.Begin(GL.QUADS);
GL.Vertex3(x1/Screen.width,y1/Screen.height,0);
GL.Vertex3(x2/Screen.width,y2/Screen.height,0);
GL.Vertex3(x3/Screen.width,y3/Screen.height,0);
GL.Vertex3(x4/Screen.width,y4/Screen.height,0);
GL.End();
GL.PopMatrix();
}}
在绘制四边形时,首先需要使用GL.Begin(GL.QUADS)方法设定渲染模型的类型为四边形,然后使用GL.Vertex3()设置
四边形每个点的坐标,最后使用GL.Eng() 方法将四边形渲染在屏幕当中移动摄像机的代码.
MoveCamera.cs
public class MoveCamera:MonoBehaviour
{
//摄像机参照的模型
public Transform target;
//摄像机距离模型的默认距离
private float distance = 2.0f;
//鼠标在x轴和y轴方向移动的角度
float x;
float y;
//限制旋转角度的最小值与最大值
float yMinLimit=-20.0f
float yMaxLimit=80.f
//x和y轴方向的移动速度
float xSpeed = 250.0f;
float ySpeed = 120.0f;
void Start(){
//初始化和y轴角度等于参照模型的角度
Vector2 angles =transform.eulerAngles;
x=angles.y;
y=angles.x;
if(rigidbody)
{
rigidbody.freezeRotation = true;
}
void LateUpdate()
{
if(target)
{
//根据鼠标的移动修改摄像机的角度
x+=Input.GetAxis("Mouse X")*xSpeed*0.02f;
y-=Input.GetAxis("Mouse Y")*ySpeed*0.02f;
y = ClampAngle(y,yMinLimit,yMaxLimit);
Quaternion rotation =Quaternion.Euler(y,x,0);
Vector3 position = rotation*new Vector3(0.0f,0.0f,(-distance))+target.position;
//设置模型的位置与旋转
transform.rotation =rotation;
transform.position =position;
}
}
float ClampAngle(float angle,float min,float max);
}
}
在LateUpdate()方法中通过鼠标的移动来观察模型,该模型的对象包寻在target变量当中。

线渲染器
线渲染器主要用于在3D世界中渲染线段,与GL图象库渲染相比,它更加专业,可以控制线段的组细程度
以及线段的数量,并且以网格对象的形式出现在3D世界中。使用线渲染器绘制线段时,必须先确定这条线段两个
端点的位置。需要说明的是,这两个点不是平面中的点而是3D世界中的点
线渲染器以组件的形式出现Unity当中,所以需要将它绑定在某个游戏对象中。这里我们在Unity导航中选择
"GameObject"--->"CreateEmpty"菜单项创建一个空的游戏对象,然后杂Hierarchy视图中选择该对象后,继续在
Unity导航菜单中选择"Component"-->"Line Render"菜单项,即可将线渲染器组件添加至游戏对象中,接着是设置
参数。
本例中绘制了3条相连的线段,它是以4个顶点确定的3条线段,并且它们首尾相接成一条线。这个线段以立体的形式
出现在3D世界中
public class Script:MonoBehaviour
{
//线段对象
private CameObject LineRenderGameObject;
//线段渲染器
private LineRenderer lineRenderer;
//设置线段的顶点数,4个点确定3条直线
private int lineLength = 4;
//记录4个点,连接一条线段
private Vector3 v0 = new Vector3(1.0f,0.0f,0.0f);
private Vector3 v1 = new Vector3(2.0f,0.0f,0.0f);
private Vector3 v2 = new Vector3(3.0f,0.0f,0.0f);
private Vector3 v3 = new Vector3(4.0f,0.0f,0.0f);

void Start()
{
//获得游戏对象
LineRenderGameObject = GameObject.Find("ObjLine");
//获得线渲染器组件
lineRenderer = (LineRendererGameObject.GetComponent("LineRenderer");
//设置线的顶点数
lineRenderer.SetVertexCount(lineLength);
//设置线的宽度
lineRenderer.SetWidth(0.1f,0.1f);
}
void Update(){
//使用4个顶点渲染3条线段
lineRender.SetPosition(0,v0);
lineRender.SetPosition(1,v1);
lineRender.SetPosition(2,v2);
lineRender.SetPosition(3,v3);
}
}
在上述代码,我们首先获取了线渲染器组件对象,然后设置顶点的数量,最后调用SetPosition()方法将线段显示在屏幕中。
SetPosition()方法的第一个参数表示每个点的ID,让它保持唯一性,第一个参数表示该顶点的3D的位置.

网格渲染
“Component”?Mesh?”Mesh Filter”菜单项与”Mesh Renderer”菜单项,即可将组件添加至游戏对象本身。
本例在屏幕中渲染了两个网格面对象。因为网格面又三角形网格顶点的位置,三角形由3个顶点组成,所以它们的规律是:
一个三角形数组长度就是3,两个三角形数组长度就是6,依次类推该数组的长度只可能是3的倍数。最后绘制网格时使用triangles数组,
数组中的ID和Vertices(网格顶点)的顶点ID一一对应。
分5等份填充
完全填充
public class script:MonoBehaviour
{
//构成三角形1的位置
Vector3 v0 = new Vector3(5,0,0);
Vector3 v1 = new Vector3(0,5,0);
Vector3 v2 = new Vector3(0,0,5);
//构成三角形2的位置
Vector3 v3 = new Vector3(-5,0,0);
Vector3 v4 = new Vector3(0,-5,0);
Vector3 v5 = new Vector3(0,0,-5);
//构成三角形1的贴图比例
Vector2 u0 = new Vector2(0,0);
Vector2 u1 = new Vector2(0,5);
Vector2 u2 = new Vector2(5,5);
//构成三角形2的贴图比例
Vector2 u3 = new Vector2(0,0);
Vector2 u4 = new Vector2(0,1);
Vector2 u5 = new Vector2(1,1);
void Start()
{
//得到网格渲染器对象
MeshFilter meshFilter =(MeshFilter)GameObject.Find("face").GetComponent(typeof(MeshFilter));
//通过渲染器对象得到网格对象
Mesh mesh = meshFilter.mesh;
//设置三角形顶点的数组,6个点表示设置了两个三角形
 mesh.Vertices = new Vector3[]{v0,v1,v2,v3,v4,v5};
//设置三角形面上的贴图比例
 mesh.uv = new Vector2[] {u0,u1,u2,u3,u4,u5};
//设置三角形索引,绘制三角形
 mesh.triangles = new int[]{0,1,2,3,4,5};
}}
代码最后的mesh.triangles表示设定三角形的索引数组,该数组中的ID表示相对顶点数组中的坐标。目前这个数组中的元素是0、1、2、3、4和5,
对应顶点数组中6个顶点坐标。因为3个点确定一个三角形面,所以这里使用定点数组中0,1,2确定了一个三角形,3,4,5又确定了一个
三角形。
  
游戏实例—控制人物移动
为了让读者更清晰地了解如何控制主角移动与播放骨骼动画,下面我们将角色控制器组件的人物动画拆开,使用代码自行实现他的行走动画。运行游戏后,按键盘键上的
“W”,”S”,”A”,”D”来移动主角。
public class Script:MonoBehaviour
{
//人物行走的方向状态
public const int HERO_UP=0;
public const int HERO_RIGHT=1;
public const int HERO_DOWN=2;
public const int HERO_LEFT=3;
//人物当前的行走方向
public int state = 0;
//人物移动速度
public int moveSpeed =10;
//初始化人物的默认位置
public void Awake(){
state =HERO_DOWN;
}
void Update(){
//获取控制的方向数据
float KeyVertical =Input.GetAxis("Vertical");
float KeyHorizontal = Input.GetAxis("Horzontal");
if(KeyVertical ==-1)
{
setHeroState(HERO_LEFT);
}else if(KeyVertical ==1)
{
//设置人物动画往右行走
setHeroState(HERO_RIGHT);
}
if(KeyHorizontal ==1)
{
setHeroState(HERO_DOWN);
}else if(KeyHorzontal ==-1)
{
setHeroState(HERO_UP);
}
if(KeyVertical ==0 &&KeyHorizontal ==0)
{
animation.Play();
}
}
public void setHeroState(int newState)
{
//根据当前人物方向与上一次备份方向计算出模型旋转的角度
int rotateValue =(newState-state)*90;
Vector3 transformValue =new Vector3();
//播放行走动画
animation.Play("walk");
//模型移动的位移的数值
switch(newState){
case HERO_UP:
transformValue = Vector3.forward*Time.deltaTime;
break;
case HERO_DOWN:
transformValue =(-Vector3.forward).Time.deltaTime;
break;
case HERO_LEFT:
transformValue =  Vector3.left*Time.deltaTime;
break;
case HERO_RIGHT:
transformValue = (-Vector3.left)*Time.deltaTime;
break;
}
//模型旋转
transform.Rotate(Vector3.up,rotateValue);
//移动人物
transform.Translate(transformValue *moveSpeed,Space.World);
state = newState;
}
}
本例使用游戏状态机将主角的行走分为4个状态:向前行走,向后行走,向左行走,向右行走。按下不同的方向键后,
使用animation.Play()方法播放行走动画,该方法的参数为动画名称,最后根据当前的行走状态计算模型的旋转角度,
使其按照正确的方向行走。
本章首先介绍了如何处理键盘与鼠标输入事件,比如按下事件,抬起事件和长按事件等,接着介绍了自定义按键事件、
模型与动画,然后介绍了如何使用GL图像库绘制2D与3D的线段与网络模型,以及线渲染器与网格渲染器的绘制方法,
最后以一个实例的形式向读者介绍如何使用键盘控制主角移动并且播放骨骼动画。