//
// unity渲染的矩阵变换全流程
坐标从模型位置转变成屏幕位置的运算过程。
整体过程
模拟演算
以橙色标记点为计算点。
模型空间
空间:建模软件
维度:3维
示例演算:
世界空间
空间:引擎 unity的transform位置
维度:3维
示例演算
公式
思路
M model 是根据cube的transform位置进行变换。
从上图可以得到数据,cube向(-1,-1,-1)进行位移,y轴旋转30°,整体缩小0.5百分比。
计算
sin(30) = 0.5
cos(30) = 0.866
观察空间
空间:摄像机
维度:3维
示例演算
公式
思路
计算视角矩阵是以正方体为原点,反向摄像机的矩阵,所以顺序是逆着的。
在unity看到的摄像机的旋转角度,在计算的方向是反方向。从方块的角度是摄像机移动了-8°。
计算
裁剪空间
空间:摄像机的照相范围
维度:3维
示例演算
公式
判断是否需要被裁剪,需要视锥体的坐标满足以下范围。在范围内不被裁剪,超出则会被裁剪。
-w ≤ x ≤ w
-w ≤ y ≤ w
-w ≤ z ≤ w
思路
有两种投影方式:正交和透视。两种投影方式计算不相同,会让屏幕看到的内容不相同。
当前从摄像机的属性:Projection 可以得知是透视投影。接下来就以投影类型进行计算。
裁剪空间到屏幕空间会进行位置裁剪。通过位置是否在视锥体内进行裁剪。
计算
Aspect 是视图的横纵比,在Unity里是Game视图里的分辨率。当前是5:4。
Fov是摄像机的开合的角度。在Unity里是Camera的FieldofView属性。当前是20。
Far和Near是摄像机的最远点和最近点。在Unity里是Camera的Clipping Planes的Near和Far属性。当前Near是0.3,Far是1000。
判断是否会被裁剪:
-9.247 ≤ 1.55 ≤ 9.247
-9.247 ≤ -2.652 ≤ 9.247
-9.247 ≤ 8.652 ≤ 9.247
位置符合视锥体范围,所以不会被裁剪。
屏幕空间
空间:屏幕
维度:2维
之前的计算都是基于三维,而屏幕呈现的是二维空间。所以这一步骤中需要先进行维度的转变。
步骤:
1.坐标进行齐次除法。
2.屏幕映射。
示例演算
公式
思路
齐次除法的作用在于将裁剪空间变换到立方体的空间中。
正交投影本来就是立方体,所以计算后数值不会有变化。而透视投影是锥体,所以计算后数值会改变。
计算后得到二维坐标。而三维坐标中的z值用于深度缓冲计算。
计算
在unity的屏幕坐标系中,原点在左下角。pixelWidth和pixelHeight是屏幕的大小。
使用unity接口:Screen.width,Screen.height 可以得到屏幕大小。
测试窗口通过输出,得知当前屏幕大小是:649,519。
演算结果
/ Unity渲染矩阵变换编辑器
效果
实现
有一个主摄像机,有一个正方体即可。然后将脚本随便挂到一个对象上,赋值正方体。
输出
运行时,会有Log输出整个流程的矩阵的数值。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PointMartixTest : MonoBehaviour
{
public Transform Cubu;
private Vector3 point;
private Matrix4x4 pointMartix;
private Matrix4x4 pointWorldMartix;
private Matrix4x4 pointCameraMartix;
private Matrix4x4 pointProjectionMartix;
private Vector2 scenePoint;
private Matrix4x4 pointSceneMartix;
private GUIStyle textGUIStyle;
private string inputX;
private string inputY;
private string inputZ;
private string rotationX;
private string rotationY;
private string rotationZ;
private Quaternion quaternion;
private Vector3 rotation;
private string positionX;
private string positionY;
private string positionZ;
private Vector3 position;
private bool showWorld;
private bool showCamera;
private bool showProjection;
private bool showScene;
void Start()
{
textGUIStyle = new GUIStyle();
textGUIStyle.fontSize = 30;
GUIStyleState normalStyleState = new GUIStyleState();
normalStyleState.textColor = Color.white;
textGUIStyle.normal = normalStyleState;
inputX = "0";
inputY = "0";
inputZ = "0";
quaternion = Cubu.rotation;
Vector3 rotation = quaternion.eulerAngles;
rotationX = rotation.x.ToString();
rotationY = rotation.y.ToString();
rotationZ = rotation.z.ToString();
Vector3 position = Cubu.position;
positionX = position.x.ToString();
positionY = position.x.ToString();
positionZ = position.x.ToString();
}
// Update is called once per frame
void Update()
{
pointMartix = new Matrix4x4(new Vector4(point.x, point.y, point.z, 1), Vector4.zero, Vector4.zero, Vector4.zero);
computePointWorld();
computePointCamera();
computePointProjection();
computePointScene();
}
private void OnGUI()
{
int firstHeight = 30;
showInputPosition(firstHeight, "cube上的位置。", ref point);
firstHeight += 50;
showRotation(firstHeight, "cube旋转角度。", ref rotation);
quaternion.eulerAngles = rotation;
Cubu.rotation = quaternion;
firstHeight += 50;
showPosition(firstHeight, "cube位置。", ref position);
Cubu.position = position;
firstHeight += 50;
if (GUI.Button(new Rect(20, firstHeight, 200, 40), "point -> world"))
{
showWorld = !showWorld;
if (showWorld)
{
showCamera = false;
showProjection = false;
showScene = false;
}
}
if (GUI.Button(new Rect(230, firstHeight, 200, 40), "world -> camera"))
{
showCamera = !showCamera;
if (showCamera)
{
showWorld = false;
showProjection = false;
showScene = false;
}
}
if (GUI.Button(new Rect(440, firstHeight, 200, 40), "camera -> projection"))
{
showProjection = !showProjection;
if (showProjection)
{
showWorld = false;
showCamera = false;
showScene = false;
}
}
if (GUI.Button(new Rect(650, firstHeight, 200, 40), "projection -> scene"))
{
showScene = !showScene;
if (showScene)
{
showWorld = false;
showCamera = false;
showProjection = false;
}
}
firstHeight += 50;
if (showWorld)
{
showMatrix4x4(firstHeight, "M (world)", Cubu.localToWorldMatrix);
showMatrix4x4(firstHeight+150, "P (world)",pointWorldMartix);
}
if(showCamera)
{
showMatrix4x4(firstHeight, "M (camera)", Camera.main.worldToCameraMatrix);
showMatrix4x4(firstHeight + 150, "P (camera)", pointCameraMartix);
}
if (showProjection)
{
showMatrix4x4(firstHeight, "M (projection)", Camera.main.projectionMatrix);
showMatrix4x4(firstHeight + 150, "P (projection)", pointProjectionMartix);
}
if (showScene)
{
GUI.Label(new Rect(20, firstHeight, 300, 30), "Screen Width:" + Screen.width + " Screen Height:" + Screen.height, textGUIStyle);
showPosition(firstHeight+30, "P (scene)", scenePoint);
GUI.Label(new Rect(scenePoint.x - 5, Screen.height - (scenePoint.y + 5), 10, 10), "*", textGUIStyle);
}
}
private void showInputPosition(float height,string title,ref Vector3 point)
{
GUI.Label(new Rect(20, height, 250, 30), title, textGUIStyle);
GUI.Label(new Rect(260, height, 20, 30), "x:", textGUIStyle);
inputX = GUI.TextField(new Rect(290, height, 60, 30), inputX, textGUIStyle);
if (!string.IsNullOrEmpty(inputX))
{
float px;
if (float.TryParse(inputX, out px))
{
point.x = px;
}
}
GUI.Label(new Rect(420, height, 20, 30), "y:", textGUIStyle);
inputY = GUI.TextField(new Rect(450, height, 60, 30), inputY, textGUIStyle);
if (!string.IsNullOrEmpty(inputY))
{
float py;
if (float.TryParse(inputY, out py))
{
point.y = py;
}
}
GUI.Label(new Rect(570, height, 20, 30), "z:", textGUIStyle);
inputZ = GUI.TextField(new Rect(620, height, 60, 30), inputZ, textGUIStyle);
if (!string.IsNullOrEmpty(inputZ))
{
float pz;
if (float.TryParse(inputZ, out pz))
{
point.z = pz;
}
}
}
private void showRotation(float height, string title, ref Vector3 point)
{
GUI.Label(new Rect(20, height, 250, 30), title, textGUIStyle);
GUI.Label(new Rect(260, height, 20, 30), "x:", textGUIStyle);
rotationX = GUI.TextField(new Rect(290, height, 60, 30), rotationX, textGUIStyle);
if (!string.IsNullOrEmpty(rotationX))
{
float px;
if (float.TryParse(rotationX, out px))
{
if (px == 0)
{
point.x = 0;
}
else
{
point.x = px;
}
}
}
GUI.Label(new Rect(420, height, 20, 30), "y:", textGUIStyle);
rotationY = GUI.TextField(new Rect(450, height, 60, 30), rotationY, textGUIStyle);
if (!string.IsNullOrEmpty(rotationY))
{
float py;
if (float.TryParse(rotationY, out py))
{
if (py == 0)
{
point.y = 0;
}
else
{
point.y = py ;
}
}
}
GUI.Label(new Rect(570, height, 20, 30), "z:", textGUIStyle);
rotationZ = GUI.TextField(new Rect(620, height, 60, 30), rotationZ, textGUIStyle);
if (!string.IsNullOrEmpty(rotationZ))
{
float pz;
if (float.TryParse(rotationZ, out pz))
{
if (pz == 0)
{
point.z = 0;
}
else
{
point.z = pz;
}
}
}
}
private void showPosition(float height, string title, ref Vector3 point)
{
GUI.Label(new Rect(20, height, 250, 30), title, textGUIStyle);
GUI.Label(new Rect(260, height, 20, 30), "x:", textGUIStyle);
positionX = GUI.TextField(new Rect(290, height, 60, 30), positionX, textGUIStyle);
if (!string.IsNullOrEmpty(positionX))
{
float px;
if (float.TryParse(positionX, out px))
{
if (px == 0)
{
point.x = 0;
}
else
{
point.x = px;
}
}
}
GUI.Label(new Rect(420, height, 20, 30), "y:", textGUIStyle);
positionY = GUI.TextField(new Rect(450, height, 60, 30), positionY, textGUIStyle);
if (!string.IsNullOrEmpty(positionY))
{
float py;
if (float.TryParse(positionY, out py))
{
if (py == 0)
{
point.y = 0;
}
else
{
point.y = py;
}
}
}
GUI.Label(new Rect(570, height, 20, 30), "z:", textGUIStyle);
positionZ = GUI.TextField(new Rect(620, height, 60, 30), positionZ, textGUIStyle);
if (!string.IsNullOrEmpty(positionZ))
{
float pz;
if (float.TryParse(positionZ, out pz))
{
if (pz == 0)
{
point.z = 0;
}
else
{
point.z = pz;
}
}
}
}
private void showMatrix4x4(float height, string title, Matrix4x4 matrix)
{
GUI.Label(new Rect(20, height, 250, height), title, textGUIStyle);
GUI.Label(new Rect(260, height, 200, 200), matrix.ToString(), textGUIStyle);
}
private void showPosition(float height, string title, Vector3 point)
{
GUI.Label(new Rect(20, height, 250, 30), title, textGUIStyle);
GUI.Label(new Rect(260, height, 200, 200), point.ToString(), textGUIStyle);
}
private void computePointWorld()
{
Matrix4x4 localToWorldMatrix = Cubu.localToWorldMatrix;
pointWorldMartix = Cubu.localToWorldMatrix * pointMartix;
Debug.Log("point -> world. \n " + "\n" + localToWorldMatrix + " \n*\n " + pointMartix + " \n=\n " + pointWorldMartix);
}
private void computePointCamera()
{
Matrix4x4 worldToCameraMatrix = Camera.main.worldToCameraMatrix;
pointCameraMartix = worldToCameraMatrix * pointWorldMartix;
Debug.Log("world -> camera. \n " + "\n" + worldToCameraMatrix + " \n*\n " + pointWorldMartix + " \n=\n " + pointCameraMartix);
}
private void computePointProjection()
{
Matrix4x4 projectionMatrix = Camera.main.projectionMatrix;
pointProjectionMartix = projectionMatrix * pointCameraMartix;
Debug.Log("camera -> projection. \n " + "\n" + projectionMatrix + " \n*\n " + pointCameraMartix + " \n=\n " + pointProjectionMartix);
}
private void computePointScene()
{
scenePoint = Vector3.zero;
float width = Screen.width;
float height = Screen.height;
float x = pointProjectionMartix.m00;
float y = pointProjectionMartix.m10;
float w = pointProjectionMartix.m30;
scenePoint.x = ((x * width) / (2 * w)) + width / 2;
scenePoint.y = ((y * height) / (2 * w)) + height / 2;
Debug.Log("projection -> scene . \n scenePoint:" + scenePoint + "\n width:" + width + " height:" + height);
}
}
//