1.前言
采用GL类以及Graphics类进行绘制图形时,都需要用到坐标变换。这跟采用图形学接口进行绘制时相同。以一个球为例,如果在坐标原点处绘制,球心坐标为0。如果在其他位置绘制,球心坐标不为0,此时球面顶点坐标需要重新计算。如果采用矩阵变换(坐标变换),将坐标原点移动到球心所在的位置,则球心仍在原点位置,球面坐标保持不变。所以我们在绘制一个图形时,先进行坐标变换,然后再绘制,绘制结束后再恢复到原来坐标系(即世界坐标系),然后再坐标变换绘制其他图形,然后再恢复到世界坐标系。
2.GL类坐标变换
此节主要针对GL类几个加载坐标系的方法来进行说明,如LoadPixelMatrix、MultMatrix等。
2.1 世界坐标系
默认为世界坐标系,如下代码为在世界坐标系下绘制正多边形。此时移动camera,图像会移动。因为图像的坐标与camera坐标无关系。
public int circleRadius = 3;
public int circleCount = 6;
public int zValue = 0;
private void DrawCircleSurface()
{
float angleDelta = 2 * Mathf.PI / circleCount;
GL.Begin(GL.TRIANGLES);
GL.Color(Color.yellow);
for (int i = 0; i < circleCount; i++)
{
float angle = angleDelta * i;
float angleNext = angle + angleDelta;
GL.Vertex3(0, 0, zValue);
GL.Vertex3(Mathf.Cos(angle) * circleRadius, Mathf.Sin(angle) * circleRadius, zValue);
GL.Vertex3(Mathf.Cos(angleNext) * circleRadius, Mathf.Sin(angleNext) * circleRadius, zValue);
}
GL.End();
}`在这里插入代码片`
2.2 局部坐标系
如果我们让图像变成某一个游戏物体的子物体(如camera),可以采用GL.MultMatrix方法。首先通过transform.localToWorldMatrix获取局部坐标到世界坐标的变换矩阵,然后通过GL.MultMatrix加载进来就可以。此时我们进行绘制时,计算的坐标都为局部坐标,最终数据会通过坐标转换为世界坐标系下结果,代码如下。如果此时再移动camera,图像则跟随camera移动。
private void DrawCircleSurfaceLocal()
{
GL.PushMatrix();
GL.MultMatrix(transform.localToWorldMatrix);
DrawCircleSurface();
GL.PopMatrix();
}
2.3 屏幕坐标系
可以通过GL.LoadPixelMatrix将图像直接绘制到屏幕上,此方法有两个重载,对应绘制到当前屏幕上和绘制到指定大小像素上。如果指定大小像素与屏幕相同,则即为绘制到当前屏幕大小。但是此时图像坐标是以像素为大小的,且z值坐标为0.。而且屏幕坐标没有负值,所以以本例正多边形为例,由于原点在0位置,所以屏幕只能显示四分之一图像。
private void DrawCircleSurfaceScreen()
{
//circleRadius = 1000; 以像素为单位
GL.PushMatrix();
//GL.LoadPixelMatrix();
GL.LoadPixelMatrix(0,screenSize.x,0,screenSize.y);
DrawCircleSurface();
GL.PopMatrix();
}
当screenSize的x与y值和屏幕分辨率相同时,与GL.LoadPixelMatrix()的效果一致。
2.4 正交坐标系
采用GL.LoadOrtho()方法可以将图像直接绘制到屏幕上,此时是正交视图,大小为1,所以坐标系数值均要小于等于1.。同样坐标值没有负值。
private void DrawCircleSurfaceOrtho()
{
//circleRadius = 1; 最大值为1
GL.PushMatrix();
GL.LoadOrtho();
DrawCircleSurface();
GL.PopMatrix();
}
2.5 视口分离
通过GL.Viewport方法可以实现绘制在局部视口内,如下所示:
private void DrawCircleSurfaceViewport()
{
GL.PushMatrix();
GL.LoadPixelMatrix();
GL.Viewport(new Rect(0, 0, Screen.width / 2, Screen.height / 2));
DrawCircleSurface();
GL.PopMatrix();
}
注意:如果坐标系采用的正交坐标系,即采用GL.LoadOrtho()方法,则视口的最大值为1,此时 GL.Viewport(new Rect(0, 0, 1.0f / 2, 1.0f / 2));
3.完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Graphics03GLMatrix : MonoBehaviour
{
public enum SpaceType
{
WORLD,
LOCAL,
SCREEN_NORMAL,
SCREEN,
VIEWPORT
}
public SpaceType type = SpaceType.WORLD;
public int circleRadius = 3;
public int circleCount = 6;
public int zValue = 0;
public Vector2 screenSize = Vector2.zero;
private void OnRenderObject()
{
SetMaterialPass();
switch (type)
{
case SpaceType.WORLD:
DrawCircleSurface();
break;
case SpaceType.LOCAL:
DrawCircleSurfaceLocal();
break;
case SpaceType.SCREEN_NORMAL:
DrawCircleSurfaceOrtho();
break;
case SpaceType.SCREEN:
DrawCircleSurfaceScreen();
break;
case SpaceType.VIEWPORT:
DrawCircleSurfaceViewport();
break;
}
}
private Material glMat;
private void SetMaterialPass()
{
if (glMat == null)
{
glMat = new Material(Shader.Find("Hidden/Internal-Colored"));
}
glMat.SetPass(0);
}
private void DrawCircleSurface()
{
float angleDelta = 2 * Mathf.PI / circleCount;
GL.Begin(GL.TRIANGLES);
GL.Color(Color.yellow);
for (int i = 0; i < circleCount; i++)
{
float angle = angleDelta * i;
float angleNext = angle + angleDelta;
GL.Vertex3(0, 0, zValue);
GL.Vertex3(Mathf.Cos(angle) * circleRadius, Mathf.Sin(angle) * circleRadius, zValue);
GL.Vertex3(Mathf.Cos(angleNext) * circleRadius, Mathf.Sin(angleNext) * circleRadius, zValue);
}
GL.End();
}
private void DrawCircleSurfaceLocal()
{
GL.PushMatrix();
GL.MultMatrix(transform.localToWorldMatrix);
DrawCircleSurface();
GL.PopMatrix();
}
private void DrawCircleSurfaceOrtho()
{
//circleRadius = 1; 最大值为1
GL.PushMatrix()