(三)GL 空间变换

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();
        GL.LoadOrtho();
        DrawCircleSurface();
        GL.PopMatrix();
    }

    private void DrawCircleSurfaceScreen()
    {
        //circleRadius = 1000; 以像素为单位
        GL.PushMatrix();
        //GL.LoadPixelMatrix();
        GL.LoadPixelMatrix(0,screenSize.x,0,screenSize.y);
        DrawCircleSurface();
        GL.PopMatrix();
    }

    private void DrawCircleSurfaceViewport()
    {
        GL.PushMatrix();
        GL.LoadPixelMatrix();
        GL.Viewport(new Rect(0, 0, Screen.width / 2, Screen.height / 2));
        DrawCircleSurface();
        GL.PopMatrix();
    }
}

原文地址:https://www.cnblogs.com/llstart-new0201/p/12315711.html