几何向量:ScreenToViewportPoint/ScreenToWorldPoint函数解析

本文主要解析了三维引擎Unity中Camera类的两个几何函数。介绍了ScreenToViewportPoint函数,即屏幕坐标转视口坐标的原理和函数设计;还讲解了ScreenToWorldPoint函数,即屏幕坐标转世界坐标,解释了二维屏幕坐标转三维的原因及转换关系,最后提到解析这些函数可为图形库开发做准备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       三维引擎中Camera类带有一系列几何函数,这里我们看一下unity中这两个Camera提供的几何函数的意义和实现:

       1.ScreenToViewportPoint

        顾名思义就是屏幕坐标转视口坐标,在渲染流程中,建模->世界->视口->裁剪->视图得到屏幕坐标系中坐标数值,那么阶段性反过来从屏幕到视口的坐标变换也好理解,屏幕的左下角(0,0)到右上角(width,height)(ps:如果为1080p屏幕的话,width=1920,height=1080)对应的视口空间左下角(0,0)到右上角(1,1),那这个就很好理解了,鼠标坐标x/width&y/height就得到屏幕中,函数设计如下:

        

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

public class StoV : MonoBehaviour
{
    void Start()
    {
        
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 apiVPos = Camera.main.ScreenToViewportPoint(Input.mousePosition);
            Vector3 vPos = StoVPoint(Input.mousePosition);
            Debug.LogFormat("apivpos = {0} vpos = {1}", apiVPos, vPos);
        }
    }

    public Vector3 StoVPoint(Vector3 smousePos)
    {
        return new Vector3(smousePos.x / Screen.width, smousePos.y / Screen.height, 0);
    }
}

  测试结果:

  

  2.ScreenToWorldPoint

     顾名思义了,就是屏幕坐标转世界坐标,从屏幕空间到视口空间到世界坐标,这里有个重要的问题我要解释一下:

     屏幕坐标明明是二维坐标,为什么可以转到三维呢?如果看了之前写的图形学原理,就会知道,三维空间中z轴经过空间转换就成了最后的深度depth信息,所以说我们要理解一个屏幕坐标对应的像素其实是包含深度depth信息的,那么我们脑海中就可以模拟出来,在世界空间到视口空间的z值转换成depth,z值处于camera的near/far裁剪面,深度depth则处于0-255,这就是对应的转换关系,下面来一个示意图:

      

      函数设计如下: 

      

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

public class StoW : MonoBehaviour
{
    [Range(1,100)]
    [SerializeField] public float depth = 1;

    void Start()
    {

    }

    void Update()
    {
        DrawViewport();
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, depth);
            Vector3 apiwpos = Camera.main.ScreenToWorldPoint(mousePos);
            Vector3 wpos = StoWWorldPos(mousePos);
            Debug.LogFormat("mousePos = {0} apiwpos = {1} wpos = {2}", mousePos, apiwpos, wpos);
        }
    }

    //画出far裁剪面的视口线段
    private void DrawViewport()
    {
        float far = Camera.main.farClipPlane;
        float hfov = Camera.main.fieldOfView / 2;
        float hhei = Mathf.Tan(hfov * Mathf.Deg2Rad) * far;
        float hwid = hhei * Camera.main.aspect;
        Vector3 world00 = CameraLocalToWorldPos(new Vector3(-hwid, -hhei, far));
        Vector3 world01 = CameraLocalToWorldPos(new Vector3(-hwid, hhei, far));
        Vector3 world11 = CameraLocalToWorldPos(new Vector3(hwid, hhei, far));
        Vector3 world10 = CameraLocalToWorldPos(new Vector3(hwid, -hhei, far));
        Debug.DrawLine(world00, world01, Color.red);
        Debug.DrawLine(world01, world11, Color.red);
        Debug.DrawLine(world11, world10, Color.red);
        Debug.DrawLine(world10, world00, Color.red);
    }

    //mainCamera处理local到world的TRS变换
    private Vector3 CameraLocalToWorldPos(Vector3 localpos)
    {
        return Camera.main.transform.TransformPoint(localpos);
    }

    //将视口空间相对坐标转到世界坐标
    //处理TRS矩阵变换
    private Vector3 StoWWorldPos(Vector3 mousePos)
    {
        return CameraLocalToWorldPos(StoWLocalPos(mousePos));
    }

    //计算视口空间中相对坐标
    private Vector3 StoWLocalPos(Vector3 mousePos)
    {
        Vector3 eyePos = Camera.main.transform.position;
        float halffov = Camera.main.fieldOfView / 2;
        float halfhei = Mathf.Tan(halffov * Mathf.Deg2Rad) * depth;
        float halfwid = halfhei * Camera.main.aspect;
        float ratiox = (mousePos.x - Screen.width / 2) / (Screen.width / 2);
        float ratioy = (mousePos.y - Screen.height / 2) / (Screen.height / 2);
        float localx = halfwid * ratiox;
        float localy = halfhei * ratioy;
        Vector3 localxyz = new Vector3(localx, localy, depth);
        return localxyz;
    }
}

  测试结果如下:

       顺便再次解释一下,首先使用camera的属性算出裁剪面的相对坐标范围,我是用红色line画出的区域就是,然后使用屏幕空间像素坐标映射到视口红色区域,得到相对xy坐标,最后得到localXYZ坐标,然后使用TRS矩阵变换到世界空间即可。

       这里解析实现Camera的函数,可以为我以后发展一下图形库开发做准备。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值