公司由于项目需要,需要对影片的画面进行裁切和缩放。由于对于裁切的需求是可通过用户需求自动改变其宽高,而Unity相机的投影矩阵是固定的,所以这里我自定义了一个投影矩阵来替换Unity的原生投影矩阵。具体用法和代码如下:

/* 创建时间:2017/4/10
 * 
 * 编写:    沈阳
 * 
 * 介绍:    1.通过相机对Quad面显示的画面进行裁切和缩放,原理是改变相机的投影矩阵来实现其功能.
 *           2.默认处于裁切状态,按Z进入裁切操作,按X进入缩放操作.
 *           3.在裁切状态下,按下上下左右箭头键进行相机的移动.
 *             在缩放状态下,按下上下左右箭头键进行相机的缩放.
 *           4.在裁切状态下,改变此脚本的m_WeightVideo和m_HeightVideo值对应改变裁切区域的大小,改变transform组件下的X,Y坐标对应改变裁切的区域
 *             在缩放状态下,改变此脚本的m_WeightVideo和m_HeightVideo值对应改变缩放的大小,
 *           5.最终的输出参数为outputTexture(变量类型为RenderTexture).
 * 
 * 使用:    将此脚本挂在Quad面的游戏物体上即可.(与此脚本想关联的脚本有ProjectionMatrix.cs,WriteAndReadXML.cs). 
 * 
 * 功能模块:1.在面Quad的游戏物体上创建子物体,并添加Camera的组件,函数为CameraControl(State.CreateCamera).
 *           2.通过移动相机来控制裁切的区域,函数为CameraControl(State.JudgmentBorder).
 *           3.通过改变相机的投影矩阵来改变相机可见区域的宽高来实现对面Quad的裁切,
 *             函数为CuttingWeightAndHeight(ref float width, ref float height).
 *           4.通过改相机的投影变换来改变相机的宽和高,同时也改变了quad的面的宽高(与缩放的效果相同),
 *             函数为ChangeCameraProjectionMatrix(ref float width, ref float height).
 *           5.对参数进行XML文件的读写(每次运行时通过读取对应的XML进行参数的配置).
 * 
 * BUG:     相机的X,Y坐标必须是(0,0),否则相机位置自动改变.
 * 
 * 公司:    ****
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraAspect : MonoBehaviour {

    public GameObject m_quad;
    public RenderTexture OutputTexture;
    public UITexture m_UITexture;
    public UIWidget m_UIWidget;

    private Camera m_camera;

    public float m_WeightVideo = 0;
    public float m_HeightVideo = 0;
    private float m_aspect = 0;
    private float m_orthographicSize = 0;
    private float m_nearClipPlane = 0;
    private float m_farClipPlane = 0;
    private float m_depth = 0;
    private float m_maxWidth = 0;
    private float m_maxHeight = 0;
    private float m_maxWidgetWidth = 0;
    private float m_maxWidgetHeight = 0;

    private int m_time = 0;        //控制释放内存代码的执行次数

    private float m_quadScale = 100.0f;
    private float m_minWidth = 50.0f;
    private float m_minHeight = 50.0f;

    private ConsultValue m_consultValue;
    private struct ConsultValue         //数值的参照值
    {
        public float width;
        public float height;     //控制刷新次数(当数值发生变化时刷新)
        public float x_scale;
        public float y_scale;    //控制quad面的缩放比例
        public Vector3 cameraInitialPos;     //相机的初始坐标
    }

    private State m_state = State.Cutting;
    private enum State
    {
        Scale,  
        Cutting,
        CameraMove,
        CreateCamera,
        JudgmentBorder,
    }

    // Use this for initialization
    void Start () {
        if (!InitParams())
            this.enabled = false;
    }
	
	// Update is called once per frame
	void Update () {
        if (Input.GetKeyDown(KeyCode.Z))
        {
            m_state = State.Cutting;
            Debug.Log("进入裁切操作!");
        }
        else if (Input.GetKeyDown(KeyCode.X))
        {
            m_state = State.Scale;
            m_consultValue.x_scale = m_WeightVideo;
            m_consultValue.y_scale = m_HeightVideo;
            Debug.Log("进入缩放操作!");
        }

        CameraControl(State.CameraMove);

        if ((m_WeightVideo != m_consultValue.width) || (m_HeightVideo != m_consultValue.height))
        {
            CameraControl(State.JudgmentBorder);

            m_consultValue.width = m_WeightVideo;
            m_consultValue.height = m_HeightVideo;
            m_aspect = m_WeightVideo / m_HeightVideo;

            ++m_time;             //清除GC
            if (m_time >= 60)
            {
                m_time = 0;
                System.GC.Collect();
                Resources.UnloadUnusedAssets();
            }

            switch (m_state)
            {
                case State.Cutting:
                    CuttingWeightAndHeight(ref m_WeightVideo, ref m_HeightVideo);
                    break;
                case State.Scale:
                    ChangeCameraProjectionMatrix(ref m_WeightVideo, ref m_HeightVideo);
                    break;
            }
        }

        m_UITexture.mainTexture = OutputTexture;

        if (m_camera.gameObject.transform.position.x != m_consultValue.cameraInitialPos.x
            || m_camera.gameObject.transform.position.y != m_consultValue.cameraInitialPos.y
            || m_camera.gameObject.transform.position.z != m_consultValue.cameraInitialPos.z)
        {
            if (m_state == State.Cutting)   //相机裁切区域的控制只有在裁切状态时才能操作
            {
                m_consultValue.cameraInitialPos = new Vector3(m_camera.gameObject.transform.position.x,
                                                              m_camera.gameObject.transform.position.y,
                                                              m_camera.gameObject.transform.position.z);

                CameraControl(State.JudgmentBorder);
            }
            else if (m_state == State.Scale)
            {
                m_camera.gameObject.transform.position = new Vector3(m_consultValue.cameraInitialPos.x, 
                                                                     m_consultValue.cameraInitialPos.y, 
                                                                     m_consultValue.cameraInitialPos.z);
            }
        }
    }

    bool InitParams()
    {
        m_quad.transform.position = new Vector3(0, 0, 10000);   //使面Quad不在主相机视野中

        CameraControl(State.CreateCamera);

        m_depth = m_camera.depth;

        if (m_camera.targetTexture == null)
        {
            if (m_WeightVideo == 0 || m_HeightVideo == 0)
            {
                Debug.Log("width or height is zero !");
                return false;
            }
        }
        else if (m_camera.targetTexture != null)
        {
            m_WeightVideo = m_camera.targetTexture.width;
            m_HeightVideo = m_camera.targetTexture.height;
        }

        m_state = State.Cutting;
        Debug.Log("进入裁切操作!(按Z进入裁切操作,按X进入缩放操作)");

        m_maxWidth = m_WeightVideo;
        m_maxHeight = m_HeightVideo;

        m_aspect = m_WeightVideo / m_HeightVideo;

        m_maxWidgetWidth = m_UIWidget.width;
        m_maxWidgetHeight = m_UIWidget.height;

        m_consultValue = new ConsultValue();
        m_consultValue.width = m_WeightVideo;
        m_consultValue.height = m_HeightVideo;
        m_consultValue.x_scale = m_WeightVideo;
        m_consultValue.y_scale = m_HeightVideo;

        Vector3 tempVection = m_camera.gameObject.transform.position;
        m_consultValue.cameraInitialPos = new Vector3(tempVection.x, tempVection.y, tempVection.z);

        m_orthographicSize = m_HeightVideo / m_quadScale / 2.0f;
        m_camera.orthographicSize = m_orthographicSize;

        m_quad.transform.localScale = new Vector3(m_WeightVideo / m_quadScale, m_HeightVideo / m_quadScale, 1);

        m_nearClipPlane = m_camera.nearClipPlane;
        m_farClipPlane = m_camera.farClipPlane;
        Matrix4x4 myMatrix = ProjectionMatrix.OrthographicProjection(m_aspect, m_orthographicSize, m_nearClipPlane, m_farClipPlane);
        m_camera.projectionMatrix = myMatrix;

        OutputTexture = new RenderTexture((int)m_WeightVideo, (int)m_HeightVideo, (int)m_depth, RenderTextureFormat.ARGB32);
        m_camera.targetTexture = OutputTexture;

        return true;
    }

    void ChangeCameraProjectionMatrix(ref float width, ref float height)  //通过改相机的投影变换来改变相机的宽和高,同时也改变了quad的面的宽高(与缩放的效果相同)
    {
        if (width < m_minWidth || height < m_minHeight)
        {
            if (width < m_minWidth)
                width = m_minWidth;

            if (height < m_minHeight)
                height = m_minHeight;
        }
        else if (width > m_maxWidth || height > m_maxHeight)
        {
            if (width > m_maxWidth)
                width = m_maxWidth;

            if (height > m_maxHeight)
                height = m_maxHeight;
        }

        float aspect = width / height;
        float orthographicSize = height / m_quadScale / 2.0f;

        float xRatioScale = width / m_consultValue.x_scale;
        float yRatioScale = height / m_consultValue.y_scale;

        float x_scale = m_maxWidth * xRatioScale / m_quadScale;
        float y_scale = m_maxHeight * yRatioScale / m_quadScale;
        if (x_scale > (m_maxWidth * 5) || y_scale > (m_maxHeight * 5))
        {
            if (x_scale > (m_maxWidth * 5))
                x_scale = m_maxWidth * 5;

            if (y_scale > (m_maxHeight * 5))
                y_scale = m_maxHeight * 5;
        }

        m_quad.transform.localScale = new Vector3(x_scale, y_scale, 1);

        m_UIWidget.width = (int)(m_maxWidgetWidth * xRatioScale);
        m_UIWidget.height = (int)(m_maxWidgetHeight * yRatioScale);

        Matrix4x4 myMatrix = ProjectionMatrix.OrthographicProjection(aspect, orthographicSize, m_nearClipPlane, m_farClipPlane);
        m_camera.projectionMatrix = myMatrix;

        OutputTexture = new RenderTexture((int)width, (int)height, (int)m_depth, RenderTextureFormat.ARGB32);
        m_camera.targetTexture = OutputTexture;
    }

    void CuttingWeightAndHeight(ref float width, ref float height)   //通过只改变相机的宽高来实现quad面的裁切
    {
        float maxWidth = m_quad.transform.localScale.x * m_quadScale;
        float maxHeight = m_quad.transform.localScale.y * m_quadScale;

        if (width > maxWidth || height > maxHeight)
        {
            if (width > maxWidth)
                width = maxWidth;

            if (height > maxHeight)
                height = maxHeight;
        }

        if (width < m_minWidth || height < m_minHeight)
        {
            if (width < m_minWidth)
                width = m_minWidth;

            if (height < m_minHeight)
                height = m_minHeight;
        }

        float aspect = width / height;
        float orthographicSize = height / m_quadScale / 2.0f;

        Matrix4x4 myMatrix = ProjectionMatrix.OrthographicProjection(aspect, orthographicSize, m_nearClipPlane, m_farClipPlane);
        m_camera.projectionMatrix = myMatrix;

        OutputTexture = new RenderTexture((int)width, (int)height, (int)m_depth, RenderTextureFormat.ARGB32);
        m_camera.targetTexture = OutputTexture;
    }

    void CameraControl(State state, float speed_cameraScale = 2.5f, float speed_cameraMove = 0.1f)          //相机的控制(用于控制裁切的区域以及生成对应的相机游戏物体)
    {
        if (state == State.CreateCamera)      //动态创建游戏物体以及相机的组件
        {
            GameObject camera = new GameObject("Camera");
            camera.transform.parent = m_quad.transform;
            camera.transform.localPosition = new Vector3(0, 0, -200);    //使相机的Z轴在面Quad的前面,从而才能渲染到面Quad的画面

            camera.AddComponent<Camera>();
            m_camera = camera.GetComponent<Camera>();
            m_camera.orthographic = true;
            m_camera.depth = 30;
        }
        else if (state == State.JudgmentBorder)     //控制相机裁切区域的边界不能超出视频画面的范围
        {
            float maxWidth = m_quad.transform.localScale.x * m_quadScale;
            float maxHight = m_quad.transform.localScale.y * m_quadScale;
            float x_boundaryPos = (maxWidth - m_WeightVideo) / m_quadScale / 2;
            float y_boundaryPos = (maxHight - m_HeightVideo) / m_quadScale / 2;

            if (m_camera.gameObject.transform.position.x < -x_boundaryPos
                || m_camera.gameObject.transform.position.x > x_boundaryPos)   //X轴边界值的判断
            {
                float y = m_camera.gameObject.transform.position.y;
                float z = m_camera.gameObject.transform.position.z;

                if (m_camera.gameObject.transform.position.x < -x_boundaryPos)
                {
                    m_camera.gameObject.transform.position = new Vector3(-x_boundaryPos, y, z);
                }
                else if (m_camera.gameObject.transform.position.x > x_boundaryPos)
                {
                    m_camera.gameObject.transform.position = new Vector3(x_boundaryPos, y, z);
                }
            }

            if (m_camera.gameObject.transform.position.y < -y_boundaryPos
                || m_camera.gameObject.transform.position.y > y_boundaryPos)  //Y轴边界值的判断
            {
                float x = m_camera.gameObject.transform.position.x;
                float z = m_camera.gameObject.transform.position.z;

                if (m_camera.gameObject.transform.position.y < -y_boundaryPos)
                {
                    m_camera.gameObject.transform.position = new Vector3(x, -y_boundaryPos, z);
                }
                else if (m_camera.gameObject.transform.position.y > y_boundaryPos)
                {
                    m_camera.gameObject.transform.position = new Vector3(x, y_boundaryPos, z);
                }
            }
        }
        else if (state == State.CameraMove)
        {
            if (Input.GetKey(KeyCode.LeftArrow))   //左箭头按键
            {
                if (m_state == State.Cutting)
                    m_camera.transform.Translate(-Vector3.right * speed_cameraMove);
                else if (m_state == State.Scale)
                    m_WeightVideo -= speed_cameraScale;
            }
            else if (Input.GetKey(KeyCode.RightArrow))   //右箭头按键
            {
                if(m_state == State.Cutting)
                    m_camera.transform.Translate(Vector3.right * speed_cameraMove);
                else if(m_state == State.Scale)
                    m_WeightVideo += speed_cameraScale;
            }

            if (Input.GetKey(KeyCode.UpArrow))   //上箭头按键
            {
                if(m_state == State.Cutting)
                    m_camera.transform.Translate(Vector3.up * speed_cameraMove);
                else if(m_state == State.Scale)
                    m_HeightVideo += speed_cameraScale;
            }
            else if (Input.GetKey(KeyCode.DownArrow))   //下箭头按键
            {
                if(m_state == State.Cutting)
                    m_camera.transform.Translate(-Vector3.up * speed_cameraMove);
                else if(m_state == State.Scale)
                    m_HeightVideo -= speed_cameraScale;
            }
        }
    }
}

自定义的投影矩阵代码如下:

using UnityEngine;
using System.Collections;

public static class ProjectionMatrix {

	public static Matrix4x4 OrthographicProjection(float whRatio, float orthoScale,float nearClipPlane, float farClipPlane)
	{

		float halfWidth = whRatio * orthoScale;
		float halfHeight = orthoScale;

		float left =  - halfWidth;
		float right =  halfWidth;
		float top =  halfHeight;
		float bottom = - halfHeight;

		Matrix4x4 m = new Matrix4x4();

		m[0,0] = 2.0f / (right - left);
		m[1,0] = 0.0f;
		m[2,0] = 0.0f;
		m[3,0] = 0.0f;

		m[0,1] = 0.0f;
		m[1,1] = 2.0f / (top - bottom);
		m[2,1] = 0.0f;
		m[3,1] = 0.0f;

		m[0,2] = 0.0f;
		m[1,2] = 0.0f;
		m[2,2] = -2.0f / (farClipPlane - nearClipPlane);
		m[3,2] = 0.0f;

		m[0,3] = -(right + left) / (right - left);
		m[1,3] = -(top + bottom) / (top - bottom);
		m[2,3] = -(farClipPlane + nearClipPlane) / (farClipPlane - nearClipPlane);
		m[3,3] = 1.0f; 
		//m = Matrix4x4.Ortho (left, right, bottom, top, nearClipPlane, farClipPlane);
		return m;
	}

	public static Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far)
	{
		float x = 2.0F * near / (right - left);
		float y = 2.0F * near / (top - bottom);
		float a = (right + left) / (right - left);
		float b = (top + bottom) / (top - bottom);
		float c = -(far + near) / (far - near);
		float d = -(2.0F * far * near) / (far - near);
		float e = -1.0F;
		Matrix4x4 m = new Matrix4x4();
		m[0, 0] = x;
		m[0, 1] = 0;
		m[0, 2] = a;
		m[0, 3] = 0;
		m[1, 0] = 0;
		m[1, 1] = y;
		m[1, 2] = b;
		m[1, 3] = 0;
		m[2, 0] = 0;
		m[2, 1] = 0;
		m[2, 2] = c;
		m[2, 3] = d;
		m[3, 0] = 0;
		m[3, 1] = 0;
		m[3, 2] = e;
		m[3, 3] = 0;
		return m;
	}

    static public Matrix4x4 PixelCorrectProjectionOffCenter(float left, float right, float bottom, float top)
    {
        float w = right - left;
        float h = top - bottom;

        float hw = w / 2;
        float hh = h / 2;

        Matrix4x4 pro = OrthographicProjection(w / h, hh, 0.3f, 1000);

        Matrix4x4 offset = Matrix4x4.identity;
        offset[0, 3] = -hw;
        offset[1, 3] = -hh;

        return pro * offset;
    }

}