Unity Camera的ScreenToWorldpoint API的实现

首先能看懂下一张图的,必须懂得矩阵,矩阵相乘的意义,矩阵乘法规则,还有透视除法,屏幕换算。这篇文章存在的意义就是回头复习知识点而存在,文章不能从根本上给一个系统性的知识。我会给几个链接,基本概括到所需要的知识点。

其实写这篇文章的目的是在computeShader里会用到一些空间矩阵,因其unity的api,computeshader是无法调用的,所以只能找出其中的原理,在computeshader里实现一遍就好,因其矩阵在两边性质和原理都是一样的,所以,剖析到矩阵级别就好,不用深究怎么获取矩阵。为了更好理解,我把相机到世界,世界到相机的矩阵,里面的旋转参数给忽略掉,坏处就是,不支持相机旋转功能。这里只是为了更好理解 屏幕到相机  相机到屏幕的原理,属于羊肠小道,正规点呢,还是别把矩阵拆分,把矩阵这块搞明白透,得到的收益也是大大的,经过一段时间后,进一步得到学习到,摄像机的旋转,也支持了,不过是用矩阵来实现的,矩阵这东西,真的是离不开图形学。再经进一步测试,发现unityCG.cginc里面涉及到的矩阵变量,在computeShader里是没法正确获取到的,猜想是先进行computeshader计算后,CPU再把正确的矩阵变量传递到Shader处理吧。而事实上也是这样子的,这里要非常注意的是,如果调用Computeshader计算,需要用到unityCG.cginc里面的变量,比如_ScreenParams参数,一定要把计算放在OnPostRender方法里,或者更靠后的函数。如果放在update,或者LateUpdate里,_ScreenParams就无法获取正确的参数

下图是横向量跟3阶矩阵相乘,竖向量跟3阶矩阵相乘,的乘法规则

 

附上几个链接,从根本上,系统上理解:

透视除法

投影矩阵

屏幕空间变换到世界空间位置的方法

Unity Shader ScreenPos详解

投影矩阵的推导,介绍,可以不懂推导,但一定要知道里面的数据怎么得来,因为如果要屏幕坐标转世界坐标的话,我们可以直接拆分矩阵进行换算。投影矩阵是整个mvp变换的核心。投影矩阵跟相机参数密切相关,弄懂它,自己实现屏幕转世界也就信手拈来了,而且对以后的相机一些底层操作,绝对得心应收。

这里解释一下屏幕换算,投影矩阵算出来的值范围是[-1,1],而屏幕范围是[0,1] , *0.5+0.5  完美转换

世界到相机的矩阵 这里为了简单一点,就先忽略矩阵里面的旋转参数,让大家更能好理解,上c#代码

  #region 相机原理代码
    /// <summary>
    /// 屏幕坐标转世界坐标,不支持相机旋转设置,必须固定相机旋转为vector3(0,0,0)
    /// </summary>
    public void MyScreenToWorldPoint(Vector3 screenPos)
    {

        Debug.Log("初始屏幕坐标为 " + screenPos);

        //屏幕坐标转为投影坐标矩阵
        Matrix4x4 p = Camera.main.projectionMatrix;
        //世界坐标到相机坐标矩阵
        Matrix4x4 v = Camera.main.worldToCameraMatrix;
      

        Vector3 apiWorldPos = Camera.main.ScreenToWorldPoint(screenPos);

        Vector3 cam = Camera.main.transform.position;

        Debug.Log("apiPos 得到的世界坐标为 " + apiWorldPos);

        Debug.Log("相机矩阵为 \r\n" + v);

        Vector3 v1 = v.MultiplyPoint(apiWorldPos);//4阶矩阵必须跟四维向量相乘,这里四维向量w自动补全为1

        Debug.Log("根据unity的api 得到的相机坐标为 " + v1);

        float cx1 = apiWorldPos.x + v.m03;//v.mo3  为相机 -cam.x 的值

        float cy1 = apiWorldPos.y + v.m13;//v.m13 为相机  -cam.y 的值

        float cz1 = -apiWorldPos.z + v.m23;//v.m13 为相机  cam.z 的值

        Debug.Log("自己算法得到的相机坐标为 " + new Vector3(cx1,cy1,cz1));

        float cx2 = apiWorldPos.x - cam.x;

        float cy2 = apiWorldPos.y - cam.y;

        float cz2 = -apiWorldPos.z + cam.z;

        Debug.Log("根据相机位置算出的世界坐标为 " + new Vector3(cx2, cy2, cz2));


        float px = screenPos.x / Screen.width;

        px = (px - 0.5f) / 0.5f;

        float py = screenPos.y / Screen.height;

        py = (py - 0.5f) / 0.5f;

        Vector3 ppos = new Vector3(px, py, screenPos.z);//得到了齐次坐标

        Debug.Log("齐次坐标 " + ppos);
        ppos = new Vector3(ppos.x * ppos.z, ppos.y * ppos.z, ppos.z);//反透视除法

        float z = ppos.z / p.m32;

        float x = ppos.x / p.m00;

        float y = ppos.y / p.m11;



        Debug.Log("得到相机坐标" + new Vector3(x, y, z));

        Vector3 camPos = Camera.main.transform.position;

        x = x + camPos.x;

        y = y + camPos.y;

        z = camPos.z - z;

        Debug.Log("得到的世界坐标为 " + new Vector3(x, y, z));

        MyWorldToScreenPos(new Vector3(x, y, z));

    }

    /// <summary>
    /// 世界坐标转屏幕坐标,不支持相机旋转设置,必须固定相机旋转为vector3(0,0,0)
    /// </summary>
    /// <param name="worldPos"></param>
    public void MyWorldToScreenPos(Vector3 worldPos)
    {
        Vector3 camPos = Camera.main.transform.position;

        Matrix4x4 p = Camera.main.projectionMatrix;

        float z = camPos.z - worldPos.z;

        float x = worldPos.x - camPos.x;

        float y = worldPos.y - camPos.y;

        Vector3 temp1 = new Vector3(x, y, z);//得到相机坐标

        Debug.Log("======================>>>>>得到的相机坐标为 " + temp1);

        float z1 = temp1.z * p.m32;

        float x1 = temp1.x * p.m00;

        float y1 = temp1.y * p.m11;

        Vector3 ppos = new Vector3(x1 / z1, y1 / z1, z1);//透视除法

        Debug.Log("======================>>>>>其次坐标为 " + ppos);

        float x2 = ppos.x * 0.5f + 0.5f;

        float y2 = ppos.y * 0.5f + 0.5f;

        x2 = x2 * Screen.width;

        y2 = y2 * Screen.height;


        Debug.Log("======================>>>>>得到的屏幕坐标为 " + new Vector3(x2, y2, 0));


    }
    #endregion

 

computeShader那边的代码如下,我们只要传递相机位置,还有投影矩阵分拆的三个参数过去就好了,支持旋转的必须传递整个矩阵过去

 

 

 

float m32;

float m00;

float m11;

float3 camPos;

//视矩阵,也就是世界到摄像机的矩阵,由C#传递过来
float4x4 v;

//投影矩阵,也就是摄像机到屏幕的矩阵,由C#传递过来
float4x4 p;

//视矩阵的逆矩阵,也就是摄像机到世界的矩阵,由C#传递过来
float4x4 iv;

//投影矩阵的逆矩阵,也就是屏幕到摄像机的的矩阵,由C#传递过来
float4x4 ip;


//把屏幕坐标转成世界坐标,不支持相机旋转
float3 ScreenToWorld(float3 p)
{
       float px = p.x /Width;

        px = (px - 0.5f) / 0.5f;

        float py = p.y / Height;

        py = (py - 0.5f) / 0.5f;

        float3 ppos = float3(px, py, p.z);//得到了齐次坐标

        ppos = float3(ppos.x * p.z, ppos.y * p.z, p.z);//反透视除法

        float z1 = ppos.z / m32;

        float x1 = ppos.x / m00;

        float y1 = ppos.y / m11;


		//相机转世界坐标
		x1 = camPos.x+x1;
		y1 = camPos.y+y1;
		z1 = camPos.z-z1;

		//得到的坐标为世界坐标
		return float3(x1,y1,z1);


}
//世界坐标转屏幕坐标 不支持相机旋转
float3 WorldToScreenPos(float3 worldPos)
{
        float z = camPos.z - worldPos.z;

        float x = worldPos.x - camPos.x;

		float y = worldPos.y - camPos.y;

        float3 temp1 = float3(x,y,z);

        float z1 = temp1 .z * m32;

        float x1 = temp1 .x * m00;

        float y1 = temp1 .y* m11;

        float3 ppos = float3(x1 / z1, y1 / z1, z1);//透视除法

        float x2 = ppos.x*0.5f + 0.5f;

        float y2 = ppos.y*0.5f + 0.5f;

        x2 = x2*Width;

        y2 = y2*Height;


      return float3(x2,y2,0);
}


//把屏幕坐标转成世界坐标,支持相机旋转
float3 ScreenToWorldMatrix(float3 p)
{
  float px = p.x / Width;

  px = (px - 0.5f) / 0.5f;

  float py = p.y / Height;

  py = (py - 0.5f) / 0.5f;

  float3 ppos = float3(px, py, p.z); //得到了齐次坐标

  ppos = float3(ppos.x * p.z, ppos.y * p.z, p.z); //反透视除法

  //反透视除法后得到了摄像机坐标
  camPos = mul(ip, float4(ppos,ppos.z));

  //下面就是从相机坐标转换到世界坐标空间
  float3 worldPos=mul(iv,float4(camPos,1)).xyz; 

  //得到的坐标为世界坐标
  return worldPos;


}

//把世界坐标点转换到屏幕坐标,支持相机旋转
//ComputeScreenPos虽然为unityCG.cginc的方法,但是不涉及到矩阵,所以可以应用该方法
float2  WorldToScreenPos2(float3 pos)
{
  float4x4 vp = mul(p,v); 

  float4  vertex = mul(vp, float4(pos, 1.0));
  
  float4  screenPos = ComputeScreenPos(vertex);

  screenPos.xy=screenPos.xy/screenPos.w;

  float width = screenPos.x * _ScreenParams.x;

  float height = screenPos.y * _ScreenParams.y;

  return float2(width,height);

}

 

 

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值