今天要完成一个计算,实现unity的,一个物体的世界坐标转换到相机视口坐标,然后旋转相机,再把视口坐标转换为世界坐标,把物体放到对应的世界坐标。
最终达到的效果,在game视窗下,物体的屏幕坐标位置始终没变。
第三课:矩阵 (opengl-tutorial.org)
矩阵的旋转,缩放,平移
Pnew = MtranslationMrotationMscale*Pold
先缩放,再旋转,最后平移
第一步是先计算出Mview矩阵。
就是相机相对于世界的矩阵,再取反。
How do I calculate a view matrix using Matrix4x4.LookAt? - Unity Answers
var viewMatrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
if (SystemInfo.usesReversedZBuffer)
{
viewMatrix.m20 = -viewMatrix.m20;
viewMatrix.m21 = -viewMatrix.m21;
viewMatrix.m22 = -viewMatrix.m22;
viewMatrix.m23 = -viewMatrix.m23;
}
Debug.Log("=========================");
Debug.Log(cam.cameraToWorldMatrix);
Debug.Log(cam.worldToCameraMatrix);
Debug.Log(viewMatrix);
Debug.Log(viewMatrix.inverse);
这个步骤,就是基本的相对坐标旋转缩放转换TRS。
但是这是不够的,还需要进行屏幕坐标转换。
Debug.Log(cam.projectionMatrix);
这个投影矩阵,经过打印确定是恒定不变的。
开始实践。
再次参考文章
Calculation behind camera.WorldToScreenPoint - Unity Answers
public Camera cam;
public Transform go;
private GameObject goShpere;
private void Awake()
{
//goShpere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
//goShpere.transform.localScale *= 0.3f;
P = cam.projectionMatrix;
}
// Update is called once per frame
void Update()
{
Debug.LogError(StringUtility.GetVec3(cam.WorldToScreenPoint(go.transform.position)));
Debug.LogError(StringUtility.GetVec3(manualWorldToScreenPoint(go.transform.position)));
//var vScreenPos1 = manualWorldToScreenPoint(go.transform.position);
//var vScreenPos2 = cam.WorldToScreenPoint(go.transform.position);
//var vScreenPos = vScreenPos1;
//var V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
//if (SystemInfo.usesReversedZBuffer)
//{
// V.m20 = -V.m20;
// V.m21 = -V.m21;
// V.m22 = -V.m22;
// V.m23 = -V.m23;
//}
////V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
////var endPos = ManualScreenPointToWorldPoint(new Vector2(vScreenPos.x, vScreenPos.y),
//// vScreenPos.z, V.inverse, P.inverse);
//var endPos = cam.ScreenToWorldPoint(vScreenPos);
//goShpere.transform.position = endPos;
}
Matrix4x4 P;
Vector3 manualWorldToScreenPoint(Vector3 wp)
{
var V = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
if (SystemInfo.usesReversedZBuffer)
{
V.m20 = -V.m20;
V.m21 = -V.m21;
V.m22 = -V.m22;
V.m23 = -V.m23;
}
// calculate view-projection matrix
//Matrix4x4 mat = P * cam.worldToCameraMatrix;
Matrix4x4 mat2 = P * V;
// multiply world point by VP matrix
Vector4 temp = mat2 * new Vector4(wp.x, wp.y, wp.z, 1f);
//冯乐乐
{
// 世界坐标 * View矩阵,得到View坐标系坐标。
var Pview = V * new Vector4(wp.x, wp.y, wp.z, 1f);
var tanHalfFov = Mathf.Tan(cam.fieldOfView * 0.5f);
//var nearClipPlaneHeight = 2 * cam.nearClipPlane * tanHalfFov;
//var farClipPlaneHeight = 2 * cam.farClipPlane * tanHalfFov;
var whAspect = cam.aspect;
float z = -Pview.z * (cam.farClipPlane + cam.nearClipPlane) / (cam.farClipPlane - cam.nearClipPlane)
- 2 * cam.farClipPlane * cam.nearClipPlane / (cam.farClipPlane - cam.nearClipPlane);
Debug.LogError("z: " + z + " " + (z / -Pview.z));
//wp.z:0.3 - 1000 -> -0.3,-1;1000,1
//w值经过了0,与书中描述一致。
}
if (temp.w == 0f)
{
// point is exactly on camera focus point, screen point is undefined
// unity handles this by returning 0,0,0
return Vector3.zero;
}
else
{
// convert x and y from clip space to window coordinates
temp.x = (temp.x / temp.w + 1f) * .5f * cam.pixelWidth;
temp.y = (temp.y / temp.w + 1f) * .5f * cam.pixelHeight;
//-0.3,1000 => 0.3,1000
float z = (temp.z + 0.3f) / (1000 + 0.3f) * (1000 - 0.3f) + 0.3f;
return new Vector3(temp.x, temp.y, z);
}
}
第一次,距离是有问题的,对照了冯乐乐的版本,发现原因,是z值经过了0点。
经过区间映射,将-0.31000映射到0.31000,解决问题。
成功代码:
using UnityEngine;
public class MatrixTest : MonoBehaviour
{
public Camera cam;
public Transform go;
private GameObject goShpere;
private void Awake()
{
goShpere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
goShpere.transform.localScale *= 1.2f;
InitWP2WPMatrix();
}
// Update is called once per frame
void Update()
{
var vScreenPos = ManualWorldToScreenPoint(go.transform.position, transform);
var endPos = ManualScreenToWorldPoint(vScreenPos, transform);
goShpere.transform.position = endPos;
}
public void InitWP2WPMatrix()
{
P = cam.projectionMatrix;
camPixelWidth = cam.pixelWidth;
camPixelHeight = cam.pixelHeight;
}
static Matrix4x4 P;
static int camPixelWidth;
static int camPixelHeight;
public static Vector4 ManualWorldToScreenPoint(Vector4 wp, Transform fakeCamera)
{
var V = Matrix4x4.TRS(fakeCamera.position, fakeCamera.rotation, Vector3.one).inverse;
if (SystemInfo.usesReversedZBuffer)
{
V.m20 = -V.m20;
V.m21 = -V.m21;
V.m22 = -V.m22;
V.m23 = -V.m23;
}
// calculate view-projection matrix
//Matrix4x4 mat = P * cam.worldToCameraMatrix;
Matrix4x4 mat2 = P * V;
// multiply world point by VP matrix
Vector4 temp = mat2 * new Vector4(wp.x, wp.y, wp.z, 1f);
if (temp.w == 0f)
{
// point is exactly on camera focus point, screen point is undefined
// unity handles this by returning 0,0,0
return Vector3.zero;
}
else
{
// convert x and y from clip space to window coordinates
temp.x = (temp.x / temp.w + 1f) * .5f * camPixelWidth;
temp.y = (temp.y / temp.w + 1f) * .5f * camPixelHeight;
//-0.3,1000 => 0.3,1000
float z = (temp.z + 0.3f) / (1000 + 0.3f) * (1000 - 0.3f) + 0.3f;
return new Vector4(temp.x, temp.y, z, temp.w);
}
}
public static Vector3 ManualScreenToWorldPoint(Vector4 sp, Transform fakeCamera)
{
float z = (sp.z - 0.3f) / (1000 - 0.3f) * (1000 + 0.3f) - 0.3f;
float x = (sp.x / camPixelWidth * 2 - 1) * sp.w;
float y = (sp.y / camPixelHeight * 2 - 1) * sp.w;
Vector4 temp = new Vector4(x, y, z, sp.w);
var V = Matrix4x4.TRS(fakeCamera.position, fakeCamera.rotation, Vector3.one).inverse;
if (SystemInfo.usesReversedZBuffer)
{
V.m20 = -V.m20;
V.m21 = -V.m21;
V.m22 = -V.m22;
V.m23 = -V.m23;
}
// calculate view-projection matrix
Matrix4x4 mat2 = V.inverse * P.inverse;
// multiply world point by VP matrix
Vector4 worldPoint = mat2 * temp;
return worldPoint;
}
}
网友评论