公司由于项目需要,需要对影片的画面进行裁切和缩放。由于对于裁切的需求是可通过用户需求自动改变其宽高,而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;
}
}