three.js中的正交投影矩阵

1,174 阅读3分钟

正交投影矩阵

通过相机世界将现实世界投射到裁剪空间中的矩阵 --- 不同深度的物体不具备近大远小的透视规则。

思路过程

裁剪空间:裁剪空间是用于显示webgl图形的空间,以画布中心为原点,宽高深都为2的正方体盒子,当图像范围超过裁剪空间(大于1或小于-1)时,图像就会不显示了。

正交投影矩阵的建立需要(left, right, top, bottom, near, far)一个已知左右,上下,近裁剪面远裁剪面的矩形盒子。

正交投影就是将现实的世界投影到裁剪空间中

裁剪空间的总长度、宽度、深度都是2,现实世界是自己定义的,我们只需要按照一定的比例去缩放

将现实世界投影到裁剪空间中的比例如下 裁剪空间中的right减去left(1 - (-1))比现实世界的 right减去left 可以计算出现实世界与裁剪空间的比值

const w = (1.0 - (-1.0)) / ( right - left ) = 2 / ( right - left )
const h = (1.0 - (-1.0)) / ( top - bottom ) = 2 / ( top - bottom )
const p = (1.0 - (-1.0)) / ( far - near ) = 2 / ( far - near )

现实世界在裁剪空间中的位移量如下 (裁剪空间中的位移量 加 裁剪空间中坐标原点(中心点)的值) 乘 此方向上的缩放比例

 const x = (( right - left )/2 +left) * w
 const y = (( top - bottom )/2 +bottom) * h
 const z = (( far - near )/2 +near) * p

这样就可以得到投影矩阵了

te[ 0 ] = w;  te[ 4 ] = 0;  te[ 8 ] = 0;  te[ 12 ] = - x;
te[ 1 ] = 0;  te[ 5 ] = h;  te[ 9 ] = 0;  te[ 13 ] = - y;
te[ 2 ] = 0;  te[ 6 ] = 0;  te[ 10 ] = -p;  te[ 14 ] = - z;
te[ 3 ] = 0;  te[ 7 ] = 0;  te[ 11 ] = 0; te[ 15 ] = 1;

源码

  makeOrthographic( left, right, top, bottom, near, far ) {
​
    const te = this.elements;
    const w = 1.0 / ( right - left );
    const h = 1.0 / ( top - bottom );
    const p = 1.0 / ( far - near );
​
    const x = ( right + left ) * w;
    const y = ( top + bottom ) * h;
    const z = ( far + near ) * p;
​
    te[ 0 ] = 2 * w;  te[ 4 ] = 0;  te[ 8 ] = 0;  te[ 12 ] = - x;
    te[ 1 ] = 0;  te[ 5 ] = 2 * h;  te[ 9 ] = 0;  te[ 13 ] = - y;
    te[ 2 ] = 0;  te[ 6 ] = 0;  te[ 10 ] = - 2 * p; te[ 14 ] = - z;
    te[ 3 ] = 0;  te[ 7 ] = 0;  te[ 11 ] = 0; te[ 15 ] = 1;
​
    return this;
​
  }

比较计算结果

{
  const projectionMatrix = new Matrix4()
  projectionMatrix.makeOrthographic( -3, 3, 4, -4, -2, 2)
  console.log(projectionMatrix.elements);
  //[0.3333333333333333, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, -0.5, 0, -0, -0, -0, 1]
}
{
  makeOrthographic(-3, 3, 4, -4, -2, 2)
  function makeOrthographic(left, right, top, bottom, near, far ) {
    const w = 2 / ( right - left )
    const h = 2 / ( top - bottom )
    const p = 2 / ( far - near )
    const x = (( right - left )/2 +left) * w
    const y = (( top - bottom )/2 +bottom) * h
    const z = (( far - near )/2 +near) * p
    const te = [];
    te[ 0 ] = w;	te[ 4 ] = 0;	te[ 8 ] = 0;	te[ 12 ] = - x;
    te[ 1 ] = 0;	te[ 5 ] = h;	te[ 9 ] = 0;	te[ 13 ] = - y;
    te[ 2 ] = 0;	te[ 6 ] = 0;	te[ 10 ] = -p;	te[ 14 ] = - z;
    te[ 3 ] = 0;	te[ 7 ] = 0;	te[ 11 ] = 0;	te[ 15 ] = 1;
    console.log(te); 
    //[0.3333333333333333, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, -0.5, 0, -0, -0, -0, 1]
  }
}

结果与源码中稍有偏差,但是不难发现其中计算结果是一样的

// 上述推导
{
  const w = 2 / ( right - left )
  const h = 2 / ( top - bottom )
  const p = 2 / ( far - near )
  const x = (( right - left )/2 +left) * w
  const y = (( top - bottom )/2 +bottom) * h
  const z = (( far - near )/2 +near) * p
}
// 源码
{
  // 当缩放比例中裁剪空间宽高深为1时
  // 化简完就和源码中一样了,在矩阵中写入缩放值([0][5][10])时要乘2
  const w = 1 / ( right - left )
  const h = 1 / ( top - bottom )
  const p = 1 / ( far - near )
  const x = (( right - left )/2 +left) * 2w 
  // (( right - left ) + 2left) * w
  // ( right + left ) * w
  
  const y = (( top - bottom )/2 +bottom) * 2h
  // ( top - bottom  + 2bottom) * h
  // ( top + bottom ) * h
  
  const z = (( far - near )/2 +near) * 2p
  // ( far - near  + 2near) * p 
  // ( far + near ) * p;
}

\