WebGL ThreeJS学习总结四

栏目: 编程工具 · 发布时间: 6年前

内容简介:WebGL ThreeJS学习总结四

通过前段时间的学习,现在已经能使用ThreeJS框架制作一些简单3D效果,对原生WebGL也有了简单的了解;在学习过程中察觉到自己在数学方面欠缺的太多,所以我决定先缓一缓技术方面的学习,补一补数学基础。

另外需要的注意的是以下内容大部分来自于《3D数学基础:图形与游戏开发》。

WebGL ThreeJS学习总结四

自然数、整数、有理数、实数;

笛卡尔坐标系、多坐标系、世界坐标系、物体坐标系、摄像机坐标系、惯性坐标系、嵌套式坐标系、描述坐标系、坐标系转换;

  • 惯性坐标系的原点和物体坐标系原点重合,但惯性坐标系的轴平行于世界坐标系的轴;引入惯性坐标系的好处在于从物体坐标系转换到惯性坐标系只需要旋转,从惯性坐标系转换到世界坐标系只需要平移。

WebGL ThreeJS学习总结四

上边列了这么多坐标系的名称或者概念,很容易让人感到疑惑, 为什么需要这么多坐标系?所有问题都放到世界坐标系中处理不就好了吗?

我的理解是有很多问题放到世界坐标系中处理反而会更复杂,比如我要计算我和吴小胖( 一只猫 )的距离,根据我的纬度和经度以及吴小胖的纬度和经度算,反而没有以我为原点拿标尺去量更简单;

文艺一点的说法,正如乔治.奥维尔在《动物庄园》所解释的那样, 所有坐标系都是平等的,但某些可能比其它的更合适

零向量、负向量、向量的大小、标量与向量相乘、标准化向量、向量的加法与减法、向量点乘、向量投影、向量叉乘

  • 两个向量点乘结果越大,那么两个向量的夹角越小,两向量越靠近;

    a = [x1,y1];
    b = [x2,y2];
    //a、b向量夹角为c
    a*b = [x1,y1]*[x2,y2] = x1*x2+y1*y2 = |a|*|b|*cos(c);
    //因此可得两个向量夹角的公式
    c = arccos(a*b/(|a|*|b|));
  • 叉乘仅适用于3D向量,两个向量叉乘得到的向量垂直于原来的两个向量,另外叉乘的优先级比点乘高;

    a = [x1,y1,z1];
    b = [x2,y2,z2];
    //a、b向量夹角为c
    a*b = [x1,y1,z1]*[x2,y2,z2] = [y1*z2-z1*y2,z1*x2-x1*z2,x1*y2-y1*x2];
    |a*b| = |a|*|b|*sin(c);
    //因此可得两个向量夹角的公式
    c = arcsin(|a*b|/(|a|*|b|));

到这基本上可以去稍微过一眼ThreeJS框架中Vect2、Vect3、Vect4等向量类的 源代码 了,另外如果不习惯英文版,也可以去看国内大神翻译的中文版 源代码

另外ThreeJS在设计时赋予Vect3、Vect2、Vect4等向量类多种意义,比如Vect3即可以表示一个3D坐标系中的点,也可以表示一个3D坐标系中向量等;这是一种有争议的设计方法,好处在于可以简化某些功能函数的设计,比如旋转函数,点可以旋转,向量也可以旋转,如果点和向量分开,那么旋转函数必须得定义接收多种参数的版本;反之则只需要定义接收一种参数的版本。坏处在于在逻辑上会带来混淆的情况。

方阵、对角矩阵、单位矩阵、矩阵转置、矩阵乘法、行向量、列向量、行列式

  • 方阵能描述任意线性变换,简单来说线性变换会保留直线,也就是线性变换的图形不可能发生弯曲和卷折;

  • 矩阵并不神秘,它只是用一种紧凑的方式来表达坐标转换所需的数学运算;

    //3D坐标系中的基向量乘以任意矩阵M的情况
    M = [m11,m12,m13,
        m21,m22,m23,
        m31,m32,m33];
    
    
    x = [1,0,0];
    y = [0,1,0];
    z = [0,0,1];
    
    
    x*M = [m11,m12,m13];
    
    
    y*M = [m21,m22,m23];
    
    
    z*M = [m31,m32,m33];
    //由此可见矩阵的每一行都可以解释为转换后的基向量

给出一个期望的变换,我们要做的一切就是计算基向量的变换,然后将变换后的基向量填入矩阵,这样就得到此次变换的矩阵形式;

矩阵与线性变换

旋转、缩放、正交投影、镜像、切变、变换组合、变换分类、行列式、余子式、矩阵的逆、标准伴随矩阵、正交矩阵、齐次矩阵

  • 3D空间绕任意轴旋转矩阵推导;

    WebGL ThreeJS学习总结四

    /**
     * 看了半天才看懂,摘录下来加深印象;
     * 首先假设该任意轴过原点,用向量n表示(向量n为单位向量),然后某个向量v绕n旋转得到新向量m;
     * 此次旋转的矩阵表示为R(n,c),也就是绕n旋转c角度;
     * 向量乘法有点乘和叉乘的区别,所以下边表达式中.表示点乘,*表示叉乘,普通乘法省略操作符。
     */
    m = v.R(n,c);
    
    
    /**
     * 将向量分解为垂直于n向量的v2和平行为n向量的v1;
     * 这么做的好处在于将复杂的3D问题转换为简单的2D问题;
     * 那么绕n旋转v就可以分为绕n旋转v1和绕n旋转v2;
     * v1平行于n,旋转之后依然为v1;
     * v2垂直于n,旋转之后为m2。
     */
    v = v1+v2;
    m = v1+m2;
    
    
    //v1是v在n上的投影
    v1 = |v1|n;
       = |v|cos(c1)n;
       = |v||n|cos(c1)/|n|n;
       = v.n/|n|n;//n为单位向量长度为1
       = (v.n)n;
    
    
    //v1+v2 = v
    v2 = v - v1;
       = v - (v.n)n;
    
    
    //向量w垂直于向量n和向量v2
    w = n*v2;//叉乘
      = n*(v-v1);
      = n*v - n*v1;//两方向相同的向量叉乘结果为向量0
      = n*v - 0;
      = n*v;
    
    
    m2 = w1+v3;
    
    
    w1 = |cos(c-90)|w;
       = |cos(c)cos(90)+sin(c)sin(90)|w;
       = sin(c)w;//180>c>90
    
    
    v3 = -|sin(c-90)|v2;
       = -|sin(c)cos(90)-cos(c)sin(90)|v2;
       = -|-cos(c)|v2;//180>c>90
       = cos(c)v2;
    
    
    m2 = sin(c)w+cos(c)v2;
       = sin(c)(n*v)+cos(c)(v - (v.n)n);
    
    
    m = v1+m2;
      = (v.n)n+sin(c)(n*v)+cos(c)(v - (v.n)n);
    
    
    /**
     * 现在已经得到了原向量v、旋转角度c、过原点旋转轴向量n以及旋转后向量m的表达式;
     * 那么如果已经旋转角度、过原点旋转轴、原向量v就可以得到旋转后向量m了;
     * 那么要得到期望变换的矩阵,只需要计算基向量的变换,然后把变换后的基向量整合起来就可以了。
     */
    
    
    //在3D坐标中,基向量就是x、y、z轴的正方向向量
    x = [1,0,0];
    n = [nx,ny,nz];
    x1 = (x.n)n+sin(c)(n*x)+cos(c)(x - (x.n)n);
    //...
    x1 = [(nx)(nx)(1-cos(c))+cos(c),(nx)(ny)(1-cos(c))+(nz)sin(c),(nx)(nz)(1-cos(c))-(ny)sin(c)];
    //后续依次代入y轴和z轴即可
  • 3D空间或者2D平面沿任意方向缩放;

    WebGL ThreeJS学习总结四

    /**
     * 设n为平行于缩放方向的单位向量,k为缩放因子,向量v为原始向量,m为缩放后的向量;
     * 我们需要推导出一个表达式,可以通过n、v、k来计算m;
     * 向量乘法有点乘和叉乘的区别,所以下边表达式中.表示点乘,*表示叉乘,普通乘法省略操作符。
     */
    
    
     v = v1+v2;
     v1 = (v.n)n;
    
    
     m2 = v2;
        = v-v1;
        = v-(v.n)n;
     m1 = kv1;
        = k(v.n)n;
    
    
     m = m1+m2;
       = v-(v.n)n+k(v.n)n;
       = v+(v.n)n(k-1);
    
    
    //2D平面
    x = [1,0];
    n = [nx,ny];
    
    
    //3D空间
    x = [1,0,0];
    n = [nx,ny,nz];
    
    
    //具体求解矩阵略...
  • 正交投影的原理就是让某个方向的缩放因子为 0

    /**
     * S表示缩放矩阵,S(n,k)表示在向量n方向上缩放,缩放因子为k。
     */
    
    
    //2D平面,向x轴投影,也就是y轴方向上缩放,缩放因子为0
    Px = S([0,1],0) = [1,0,
                       0,0];
    
    
    //3D空间,向xy平面投影,也就是z轴方向上缩放,缩放因子为0
    Pxy = S([0,0,1],0) = [1,0,0,
                          0,1,0,
                          0,0,0];
    
    
    //任意方向的投影矩阵略,可以根据沿任意方向的缩放矩阵推导。
  • 镜像也叫反射,其作用是将物体沿直线(2D)或者平面(3D)翻折,根据缩放公式使缩放因子为 -1 ,可以得到镜像矩阵;

  • 切变是一种坐标系扭曲的变换,切变的时候角度会发生变化,但是面积或者体积却保持不变,基本思想是将某一坐标的乘积加到另一个上;

    //2D平面中,将y乘以某个系数s,然后加到x上 x1 = sy+x
    Hx(s) = [1,0,
             s,1];
    
    
    //3D空间中,将z乘以不同的系数s、t,然后分别加到x、y上 x1 = sz+x y1 = tz+y
    Hxy(s,t) = [1,0,0,
                0,1,0,
                s,t,1];
  • 变换组合;

    /**
     * 设想世界坐标系中有一个任意方向,任意位置的物体,我们要把它渲染到任意方向,任意位置的摄像机中去,为了做到这一点必须将物体上所有的顶点从物体坐标系变换到世界坐标系,然后再由世界坐标系变换到摄像机坐标系。
     */
    P世界 = P物体*M(物体->世界);
    P摄像机 = P世界*M(世界->摄像机);
           = P物体*M(物体->世界)*M(世界->摄像机);
           = P物体*(M(物体->世界)*M(世界->摄像机));//使用组合变换矩阵效率更高
  • 变换分类(线性变换、仿射变换、可逆变换、等角变换、正交变换、刚体变换);

  • 假设矩阵M有r行、c列,从中移除第i行和第j列后剩下的矩阵称为M的余子式,而余子式矩阵有符号的行列式则称为M的代数余子式;

  • 任意方阵都存在一个标量,称为该标量的行列式,行列式有很多有趣的几何解释,在2D中矩阵的行列式等于以基向量为边的平行四边形的有符号的面积;在3D中矩阵的行列式等于以基向量为边的平行六面体的有符号体积;

    /**
     * 从矩阵中任意选择一行或者一列,对该行或者该列中的每个元素都乘以各自的代数余子式,
     * 这些乘积的和就是该矩阵的行列式。
     */
    
    
    //2*2矩阵行列式
    [m11,m12,
     m21,m22] = m11*m12-m12*m21;
    
    
    //3*3矩阵行列式
    [m11,m12,m13,
     m21,m22,m23,
     m31,m32,m33] = m11*|[m22,m23,m32,m33]|-m12*|[m21,m23,m31,m33]|+m13|[m21,m22,m31,m32]|
    
    
    //以此类推
  • 某个矩阵的代数余子式矩阵的转置矩阵被称为该矩阵的标准伴随矩阵;

  • 并非所有矩阵都是有逆的,如果一个矩阵有逆矩阵,那么称它为可逆矩阵或者非奇异矩阵;如果一个矩阵没有逆矩阵,那么称它为非可逆矩阵或者奇异矩阵;奇异矩阵的行列式为0,非奇异矩阵的行列式不为0;

可逆矩阵的逆矩阵为其标准伴随矩阵除以其行列式,这种方式适合低阶可逆矩阵求逆矩阵;其实还存在其他方法比如高斯消元法,适用于高阶可逆矩阵求逆矩阵;

矩阵转置的逆等于矩阵逆的转置;

矩阵乘积的逆等于矩阵逆的相反顺序的乘积,扩展到多个矩阵也适用;

矩阵的逆在几何上非常有用,因为它可以使得我们可以计算变换的反向或者相反变换,从而撤销原变换。

  • 若方阵M是正交,当且仅当方阵和其转置矩阵的乘积等于单位矩阵;

这是一条非常有用的性质,因为在实际应用中经常需要计算矩阵的逆,而3D图形计算中正交矩阵出现得又是如此频繁,例如旋转和镜像矩阵,如果知道某个矩阵是正交的,就可以避免计算逆矩阵了,大大减少计算量。

计算逆矩阵时,仅在预先知道矩阵是正交的情况下才能利用正交矩阵的优点,如果预先不知道,检查正交性通常是浪费时间。

有时候可能得到略微得到违反正交性的矩阵,例如外部坏数据或者浮点数运算等,这种情况下需要做矩阵正交化,得到一个正交矩阵,这个矩阵要尽可能的和原矩阵相同(至少希望是这样);

构造一组正交基向量的标准算法是施密特正交化,它的基本思想是,对每一行,从中减去它平行于已处理过行的部分,最后得到垂直向量。

  • 4*4齐次矩阵只是3D运算的一种方便记法而已,并不代表四维空间;

3D中的方位与角位移

  • 欧拉角

简单来说欧拉角的基本思想就是任何角位移都可以分解为绕三个互相垂直的轴的三个旋转组成,任意三个轴和任意顺序都可以,但最有意义的是使用笛卡尔坐标系( 旋转物体自身的坐标系,而不是世界坐标系 )并按一定顺序所组成的旋转序列;

作为前端工程师我们得知道手机里边陀螺仪设备所返回的数据,就是一个欧拉角位移数据;

WebGL ThreeJS学习总结四

alpha表示设备绕Z轴旋转的角度,范围为0~360;

beta表示设备绕X轴旋转的角度,范围为-180~180;

gamma表示设备绕Y轴旋转的角度,范围为-90~90;

这里存在几个问题:

其一:为什么每个角度的范围不一致?

首先得明确一点为什么要有范围,主要是因为如果没有范围,比如alpha为10和370其实是等价的,一个角位移存在多个描述会导致一些麻烦( 可以思考下会有什么麻烦? );再来就是为什么不同方向的角位移限制不一致,其实原因还是基于上述理由,如果都限制为0~360,还是有些角位移有多种表示,导致连一些基本的问题,比如两组欧拉角代表的角位移是否相同都很难回答。

至于为什么范围分别是0~360、-180~180、-90~90,这里就不过多展开了,同时也压根不准备证明欧拉角定理(欧拉定理是以瑞士数学家莱昂哈德·欧拉命名,于1775年,欧拉使用简单的几何论述证明了这定理)。

其二:怎么判断手机陀螺仪的顺序?

很简单,范围越大表示次序越靠前,因为最外层的运动范围更大一些,也就是说手机陀螺仪欧拉角的顺序是 ZXY

说到欧拉角还有一个经典的问题得说下:

万向节死锁

简单来说就是在旋转过程中有两个轴重合导致,失去一个维度,导致此后的角位移没办法用欧拉角表示,也就是说欧拉角失效了。

优酷里边有个视频教程讲的挺清楚的 欧拉旋转

尽管矩阵有一些缺点,比如占用了更多的内存,不直观,可能会出现病态矩阵的情况,但是当和图形API交流时,最终必须用矩阵来描述所需的转换,因此我们得了解如何将欧拉角转换为矩阵。

WebGL ThreeJS学习总结四

既然欧拉角可以转换为矩阵,那么矩阵可以转换为欧拉角吗?


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程珠玑(续)(修订版)

编程珠玑(续)(修订版)

【美】Jon Bentley 乔恩•本特利 / 钱丽艳、刘田 / 人民邮电出版社 / 2015-2 / CNY 35.00

历史上最伟大的计算机科学著作之一 融深邃思想、实战技术与趣味轶事于一炉的奇书 带你真正领略计算机科学之美 多年以来,当程序员们推选出最心爱的计算机图书时,《编程珠玑》总是位于前列。正如自然界里珍珠出自细沙对牡蛎的磨砺,计算机科学大师Jon Bentley以其独有的洞察力和创造力,从磨砺程序员的实际问题中凝结出一篇篇不朽的编程“珠玑”,成为世界计算机界名刊《ACM通讯》历史上最受欢......一起来看看 《编程珠玑(续)(修订版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具