线性代数:裁剪变换(投影变换)(二)

  紧接上一篇:https://blog.csdn.net/yinhun2012/article/details/80336487
  之前我们推导完裁剪变换的矩阵T,这次我们就来对该矩阵T进行程序验证和演示,毕竟我们学习图形学就是用来写程序的,把学到的知识最终使用到程序上,才是最终目标。
  接下来就让我们打开unity开始编写代码,首先要做的就是创建一个视椎体模型了,因为我们要形象观察眼睛空间,当然我又不是美术,3dmax又不会用,只能用程序去创建了,这里可以看一下unity自带的camera的视椎体,如下图:
camera_frustum
  大概就是这么个样子,前面画图小伙伴们大致也了解过了,这里我继续描绘一下我们大致要创建的模型网格的顶点坐标和拓扑信息等参数,如下图:
draw_frustum
  这里我标注好了顶点名和拓扑三角标号(这里提示:n(near) f(far) b(bottom) t(top) r(right) l(left)那么,比如nbl就是near面的左下角),接下来就写c#代码创建一个frustum_mesh,代码如下:

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

[ExecuteInEditMode]
public class TestFrustumMesh : MonoBehaviour
{

    //构建视椎体的基本参数
    float _Near = 3.0f;
    float _Far = 10.0f;
    float _L = -4.0f;
    float _R = 4.0f;
    float _T = 3.0f;
    float _B = -3.0f;
    //创建的网格
    Mesh mMesh;

    void Start()
    {
        CreateFrustumMesh();
    }

    void CreateFrustumMesh()
    {
        //构建视椎体near四个顶点和far面四个顶点
        float far2near = _Far / _Near;
        //这里我们按照逆时针排序顶点
        Vector3 nbl = new Vector3(_L, _B, _Near);
        Vector3 nbr = new Vector3(_R, _B, _Near);
        Vector3 ntr = new Vector3(_R, _T, _Near);
        Vector3 ntl = new Vector3(_L, _T, _Near);
        //这里我们按照逆时针排序顶点
        Vector3 fbl = new Vector3(_L * far2near, _B * far2near, _Far);
        Vector3 fbr = new Vector3(_R * far2near, _B * far2near, _Far);
        Vector3 ftr = new Vector3(_R * far2near, _T * far2near, _Far);
        Vector3 ftl = new Vector3(_L * far2near, _T * far2near, _Far);
        mMesh = new Mesh();
        Vector3[] vertices = new Vector3[8] {
               nbl,
               nbr,
               ntr,
               ntl,
               fbl,
               fbr,
               ftr,
               ftl
        };
        int[] triangles = new int[] {
               0,1,2,
               0,2,3,
               0,1,5,
               0,5,4,
               1,5,6,
               1,6,2,
               2,6,7,
               2,7,3,
               0,3,7,
               0,7,4,
               4,5,6,
               4,6,7,
        };
        Vector2[] uvs = new Vector2[8]
        {
            new Vector3(0, 0),
            new Vector3(1, 0),
            new Vector3(0, 1),
            new Vector3(1, 1),
            new Vector3(0, 0),
            new Vector3(1, 0),
            new Vector3(0, 1),
            new Vector3(1, 1),
        };
        mMesh.vertices = vertices;
        mMesh.triangles = triangles;
        mMesh.uv = uvs;
        GetComponent<MeshFilter>().sharedMesh = mMesh;
    }
}

  绑定物体看下效果,如下图:
csharp_mesh
  接下来我们就通过前面我们推导出的裁剪矩阵去计算出变换后的视椎体顶点,代码如下:

Vector3 getClipMatrixVector4(Vector3 vPos)
    {
        Vector4 vPos4 = new Vector4(vPos.x, vPos.y, vPos.z, 1);
        float _Z = vPos4.z;
        Matrix4x4 clipMat = new Matrix4x4();
        clipMat.m00 = 2 * _Near / ((_R - _L) * _Z);
        clipMat.m01 = 0;
        clipMat.m02 = 0;
        clipMat.m03 = -(_R + _L) / (_R - _L);

        clipMat.m10 = 0;
        clipMat.m11 = 2 * _Near / ((_T - _B) * _Z);
        clipMat.m12 = 0;
        clipMat.m13 = -(_T + _B) / (_T - _B);

        clipMat.m20 = 0;
        clipMat.m21 = 0;
        clipMat.m22 = -2 / (_Far - _Near);
        clipMat.m23 = (_Far + _Near) / (_Far - _Near);

        clipMat.m30 = 0;
        clipMat.m31 = 0;
        clipMat.m32 = 0;
        clipMat.m33 = 1;
        Vector4 cPos4 = clipMat * vPos4;
        return new Vector3(cPos4.x, cPos4.y, cPos4.z);
    }

  这个函数把原始的视椎体的顶点经过矩阵变换得到裁剪空间的顶点。接下来为了方便起见,我创建两套顶点坐标信息,一套是眼睛空间视椎体的顶点坐标,一套是变换后的裁剪空间的顶点坐标,代码如下:

/// <summary>
    /// 填充眼睛空间视椎体顶点数据
    /// </summary>
    void FillViewSpaceVertex()
    {
        //构建视椎体near四个顶点和far面四个顶点
        float far2near = _Far / _Near;
        //这里我们按照逆时针排序顶点
        Vspace_n_b_l = new Vector3(_L, _B, _Near);
        Vspace_n_b_r = new Vector3(_R, _B, _Near);
        Vspace_n_t_r = new Vector3(_R, _T, _Near);
        Vspace_n_t_l = new Vector3(_L, _T, _Near);
        //这里我们按照逆时针排序顶点
        Vspace_f_b_l = new Vector3(_L * far2near, _B * far2near, _Far);
        Vspace_f_b_r = new Vector3(_R * far2near, _B * far2near, _Far);
        Vspace_f_t_r = new Vector3(_R * far2near, _T * far2near, _Far);
        Vspace_f_t_l = new Vector3(_L * far2near, _T * far2near, _Far);
    }

    /// <summary>
    /// 填充标准设备空间顶点数据
    /// </summary>
    void FillNDCSpaceVertex()
    {
        //构建视椎体near四个顶点和far面四个顶点
        float far2near = _Far / _Near;
        //这里我们按照逆时针排序顶点
        Vspace_n_b_l = new Vector3(_L, _B, _Near);
        Vspace_n_b_r = new Vector3(_R, _B, _Near);
        Vspace_n_t_r = new Vector3(_R, _T, _Near);
        Vspace_n_t_l = new Vector3(_L, _T, _Near);
        //这里我们按照逆时针排序顶点
        Vspace_f_b_l = new Vector3(_L * far2near, _B * far2near, _Far);
        Vspace_f_b_r = new Vector3(_R * far2near, _B * far2near, _Far);
        Vspace_f_t_r = new Vector3(_R * far2near, _T * far2near, _Far);
        Vspace_f_t_l = new Vector3(_L * far2near, _T * far2near, _Far);

        NDCspace_n_b_l = getClipMatrixVector4(Vspace_n_b_l);
        NDCspace_n_b_r = getClipMatrixVector4(Vspace_n_b_r);
        NDCspace_n_t_r = getClipMatrixVector4(Vspace_n_t_r);
        NDCspace_n_t_l = getClipMatrixVector4(Vspace_n_t_l);
        NDCspace_f_b_l = getClipMatrixVector4(Vspace_f_b_l);
        NDCspace_f_b_r = getClipMatrixVector4(Vspace_f_b_r);
        NDCspace_f_t_r = getClipMatrixVector4(Vspace_f_t_r);
        NDCspace_f_t_l = getClipMatrixVector4(Vspace_f_t_l);
    }

  最后就简单了,用update去处理两套坐标信息的插值变换,然后生成实时变换的网格,就能看到效果了,如下所示:

void Update()
    {
        if (!lerp_reverse)
        {
            lerp_time += Time.deltaTime / 2.0f;
            if (lerp_time >= 1.0f)
            {
                lerp_reverse = true;
            }
        }
        else
        {
            lerp_time -= Time.deltaTime / 2.0f;
            if (lerp_time <= 0.0f)
            {
                lerp_reverse = false;
            }
        }
        Vector3 lerp_nbl = Vector3.Lerp(Vspace_n_b_l, NDCspace_n_b_l, lerp_time);
        Vector3 lerp_nbr = Vector3.Lerp(Vspace_n_b_r, NDCspace_n_b_r, lerp_time);
        Vector3 lerp_ntr = Vector3.Lerp(Vspace_n_t_r, NDCspace_n_t_r, lerp_time);
        Vector3 lerp_ntl = Vector3.Lerp(Vspace_n_t_l, NDCspace_n_t_l, lerp_time);
        Vector3 lerp_fbl = Vector3.Lerp(Vspace_f_b_l, NDCspace_f_b_l, lerp_time);
        Vector3 lerp_fbr = Vector3.Lerp(Vspace_f_b_r, NDCspace_f_b_r, lerp_time);
        Vector3 lerp_ftr = Vector3.Lerp(Vspace_f_t_r, NDCspace_f_t_r, lerp_time);
        Vector3 lerp_ftl = Vector3.Lerp(Vspace_f_t_l, NDCspace_f_t_l, lerp_time);
        UpdateFrustumMesh(lerp_nbl, lerp_nbr, lerp_ntr, lerp_ntl, lerp_fbl, lerp_fbr, lerp_ftr, lerp_ftl);
    }

  这里的UpdateFrustumMesh只是将坐标信息通过参数传递的形式改了一下,原理和最上面创建frustum是一样的,最后得到我们想要的比较形象的变换效果,如下图:
gif
  这时候我们就得到了推导出的裁剪矩阵的效果了,也就是将视椎体变换成Z轴相反的2*2*2标准正方体。
  demo下载地址:https://download.csdn.net/download/yinhun2012/10464281
  这里顺便说一下,下篇将使用fov进行新的裁剪矩阵推导和程序演算

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/80601809