unity3d 图形库的使用教程

2015年03月03日 10:21 0 点赞 0 评论 更新于 2025-11-21 16:35

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轴和y轴的最大值均为1,并非按像素计算。因此,在GL图象库的平面坐标系中,每个点的横坐标和纵坐标都应为0到1之间的浮点数,真实的像素坐标需根据这个浮点数来计算。

例如,当前游戏屏幕的像素宽高是500*500,在GL图象库平面上选择一个点(0.5f, 0.5f),那么这个点的真实像素的横坐标和纵坐标计算如下:

  • 横坐标:500(屏幕宽)* 0.5(x坐标) = 250
  • 纵坐标:500(屏幕高)* 0.5(y坐标) = 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.Vertex(new Vector3(x1 / Screen.width, y1 / Screen.height, 0));
GL.Vertex(new Vector3(x2 / Screen.width, y2 / Screen.height, 0));
}
}

7.5.2实例——绘制曲线

本例通过GL图象库记录鼠标移动的轨迹,并将其以曲线的形式显示在屏幕中。具体实现原理是:记录鼠标在Game视图中移动时每一点的坐标,然后将这些坐标存储在链表中,使用绘制方法OnPostRender()遍历链表中记录的鼠标坐标点,最后通过GL图象库绘制线段的方法将这些点两两连成线段。

以下是实现代码:

public class Script : MonoBehaviour
{
// 绘制线段材质
public Material material;
private List<Vector3> 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.Vertex(new Vector3(x1 / Screen.width, y1 / Screen.height, 0));
GL.Vertex(new Vector3(x2 / Screen.width, y2 / Screen.height, 0));
}
}

在上述代码中,我们通过Update()方法获取当前鼠标的位置,将每帧的鼠标位置存储在lineInfo链表中,然后在OnPostRender()中遍历这个链表,将链表中记录的鼠标坐标点连接起来绘制在屏幕中。

绘制四边形

在平面内,由不在同一条直线上的四条线段首尾顺序相接组成的图形就是四边形。要确定平面中的一个四边形,需要知道4个点,然后将这4个点连接起来。在GL中绘制四边形,需要使用GL.Begin(GL.QUADS)方法,该方法的参数表示要绘制的图形为四边形。如果设置的4个点在一条直线上,或者只设置了其中3个点,或者两个点重叠,导致无法构成一个四边形,程序将无法绘制该图形,这一点需要读者注意。

本例共绘制了三组几何图形——两个正四边形和一个无规则四边形。以下是实现代码:

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.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.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.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()方法用于绘制正四边形,DrawQuads()方法用于绘制无规则四边形。在上述代码的最后,我们使用GL.End()方法将绘制的四边形显示在屏幕中。

7.5.4绘制三角形

绘制三角形之前,需要确定平面中的3个点,并且保证这3个点能构成一个三角形,然后将3个点首尾连接起来。绘制三角形时,可以使用GL.Begin(GL.TRIANGLES)方法,该方法的参数表示绘制的图形为三角形。本例在屏幕中央绘制了一个正三角形,以下是具体代码:

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.TRIANGLES);
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世界中绘制三个平面四边形。为了让读者更方便看出立体效果,我们在Game视图中添加了一个立方体组件作为视图的参照物。通过随时移动鼠标来修改摄像机朝向的位置,可以观察它们之间的区别。圆圈内就是使用GL绘制的图形,它会随着摄像机的位置改变而发生移动。

以下是具体代码:

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.End()方法将四边形渲染在屏幕中。以下是移动摄像机的代码:

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.0f;
// x和y轴方向的移动速度
float xSpeed = 250.0f;
float ySpeed = 120.0f;

void Start()
{
// 初始化x和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)
{
if (angle < -360F)
angle += 360F;
if (angle > 360F)
angle -= 360F;
return Mathf.Clamp(angle, min, max);
}
}

LateUpdate()方法中,我们通过鼠标的移动来观察模型,该模型的对象包含在target变量当中。

线渲染器

线渲染器主要用于在3D世界中渲染线段。与GL图象库渲染相比,它更加专业,可以控制线段的粗细程度以及线段的数量,并且以网格对象的形式出现在3D世界中。使用线渲染器绘制线段时,必须先确定这条线段两个端点的位置。需要注意的是,这两个点不是平面中的点,而是3D世界中的点。

线渲染器以组件的形式出现在Unity中,所以需要将它绑定到某个游戏对象中。我们可以在Unity导航中选择"GameObject" --> "CreateEmpty"菜单项创建一个空的游戏对象,然后在Hierarchy视图中选择该对象,继续在Unity导航菜单中选择"Component" --> "Line Renderer"菜单项,即可将线渲染器组件添加至游戏对象中,接着进行参数设置。

本例中绘制了3条相连的线段,它由4个顶点确定,并且首尾相接成一条线。以下是实现代码:

public class Script : MonoBehaviour
{
// 线段对象
private GameObject 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 = LineRenderGameObject.GetComponent<LineRenderer>();
// 设置线的顶点数
lineRenderer.positionCount = lineLength;
// 设置线的宽度
lineRenderer.startWidth = 0.1f;
lineRenderer.endWidth = 0.1f;
}

void Update()
{
// 使用4个顶点渲染3条线段
lineRenderer.SetPosition(0, v0);
lineRenderer.SetPosition(1, v1);
lineRenderer.SetPosition(2, v2);
lineRenderer.SetPosition(3, v3);
}
}

在上述代码中,我们首先获取了线渲染器组件对象,然后设置顶点的数量,最后调用SetPosition()方法将线段显示在屏幕中。SetPosition()方法的第一个参数表示每个点的ID,需保持唯一性,第二个参数表示该顶点的3D位置。

网格渲染

在Unity中,选择“Component” -> “Mesh” -> “Mesh Filter”菜单项与“Mesh Renderer”菜单项,即可将组件添加至游戏对象本身。

本例在屏幕中渲染了两个网格面对象。因为网格面由三角形组成,三角形由3个顶点构成,所以其规律是:一个三角形数组长度为3,两个三角形数组长度为6,依次类推,该数组的长度只能是3的倍数。最后绘制网格时使用triangles数组,数组中的ID和Vertices(网格顶点)的顶点ID一一对应。

以下是实现代码:

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 = GameObject.Find("face").GetComponent<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("Horizontal");

if (KeyVertical == -1)
{
setHeroState(HERO_LEFT);
}
else if (KeyVertical == 1)
{
// 设置人物动画往右行走
setHeroState(HERO_RIGHT);
}

if (KeyHorizontal == 1)
{
setHeroState(HERO_DOWN);
}
else if (KeyHorizontal == -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的线段与网络模型,以及线渲染器与网格渲染器的绘制方法,最后以一个实例的形式向读者介绍如何使用键盘控制主角移动并且播放骨骼动画。

作者信息

boke

boke

共发布了 3994 篇文章