【问题标题】:How to unproject a vector in 3d如何在 3d 中取消投影矢量
【发布时间】:2017-05-09 14:53:08
【问题描述】:

我正在从头开始开发 3D 引擎,并且我正在尝试取消投影矢量。我使用自己的数学库ALMath.js。据我所知,要将 2d 屏幕坐标转换为 3d 世界坐标,我需要将画布中 x 和 y 坐标形成的矢量乘以 ViewProjection Matrix 的倒数。这是 unproject 的代码:

unproject : function (vector){
    var viewMatrix = camera.viewMatrix;
    var projectionMatrix = camera.projectionMatrix;

    var viewProjection = viewMatrix.multiply(projectionMatrix);
    var inverseViewProjection = viewProjection.getInverse();

    var x = ((vector.x -0) / (AL3D.width)) *2 -1;
    var y = ((vector.y -0) / (AL3D.height)) * 2 -1;
    var z = 2*vector.z-1;
    var w = 1;
    var vec4 = new ALMath.Vector4(x,y,z,w);
    var transformedVector = inverseViewProjection.multiplyByVector4(vec4);
    var wordCoords = new ALMath.Vector3(transformedVector.x/transformedVector.w,transformedVector.y/transformedVector.w,transformedVector.z/transformedVector.w);
    return wordCoords;
  }

ALMath 库运行良好。我在所有引擎周围使用它(计算模型视图投影,创建投影矩阵,做逆......)并且效果很好。事实上,我使用Octave(替代matlab)检查操作的结果,结果与ALMath 相同。

问题是如果我点击左上角:

canvas.addEventListener('click', function(event) {
            var rect = canvas.getBoundingClientRect();
            var x = event.pageX - rect.left,
            y = event.pageY - rect.top;
            var vector = camera.unproject(new ALMath.Vector3(x,y,0));
         });

使用x = 0y = 2 我得到vector = (-0.12131, -0.25894, -0.79409),我知道这是错误的,因为如果我将立方体网格设置在那个位置,我会发现这不是左上角。

我在相机类中编写了一个lookAt函数

lookAt : function (eye, target, up)

作为一个例子,我展示了x = 0y = 2octave 的操作。

首先我将我的相机设置如下:

camera = new AL3D.PerspectiveCamera(40, window.innerWidth/window.innerHeight);
        camera.lookAt(new ALMath.Vector3(), new ALMath.Vector3(0,-0.5,-2), new ALMath.Vector3(0,1,0));

这是octave 中与javascript 代码结果匹配的逐步计算

viewMatrix =

   1.00000   0.00000   0.00000   0.00000
   0.00000   0.97014   0.24254   0.00000
   0.00000  -0.24254   0.97014   0.00000
   0.00000   0.00000   0.00000   1.00000

projectionMatrix =

   1.37374   0.00000   0.00000   0.00000
   0.00000   2.82610   0.00000   0.00000
   0.00000   0.00000  -1.00020  -0.20002
   0.00000   0.00000  -1.00000   0.00000

octave:7> viewProjectionMatrix = viewMatrix * projectionMatrix 
viewProjectionMatrix =

   1.37374   0.00000   0.00000   0.00000
   0.00000   2.74171  -0.24258  -0.04851
   0.00000  -0.68543  -0.97034  -0.19405
   0.00000   0.00000  -1.00000   0.00000

octave:8> inverseViewProjectionMatrix = inv(viewProjectionMatrix)
inverseViewProjectionMatrix =

   0.72794   0.00000   0.00000  -0.00000
   0.00000   0.34328  -0.08582   0.00000
   0.00000   0.00000   0.00000  -1.00000
   0.00000  -1.21256  -4.85023   5.00050

AL3D.width = 1366
AL3D.height = 664
x = -1
y = -0.9939759036144579
z = -1
w = 1

octave:9> vector = [ -1 -0.9939759036144579 -1 1]
vector =

  -1.00000  -0.99398  -1.00000   1.00000

octave:10> transformedVector = vector * inverseViewProjectionMatrix 
transformedVector =

  -0.72794  -1.55377  -4.76492   6.00050
// Perspective division
octave:12> result = [ transformedVector(1)/transformedVector(4) transformedVector(2)/transformedVector(4) transformedVector(3)/transformedVector(4)]
result =

  -0.12131  -0.25894  -0.79409

也许我忘记了什么,但我不知道。我的逻辑有什么问题。谢谢。

编辑:问题似乎出在视图矩阵上。这是我的视图矩阵的代码:

lookAt : function(eye, target, up){
        var eye = eye || new ALMath.Vector3();
        var up = up || new ALMath.Vector3();
        var target = target || new ALMath.Vector3();

        var c = this.components;

        var z = target.sub(eye);
        z = z.normalize();

        var x = z.cross(up);
        x = x.normalize();

        var y = x.cross(z);
        y = y.normalize();

        c[0] = x.x; c[1] = x.y; c[2] = x.z; 
        c[4] = y.x; c[5] = y.y; c[6] = y.z; 
        c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; 
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);

        return this;
    },

这是用于投影:

perspectiveProjection : function ( fov, aspect, zNear, zFar ) {
        var a = aspect;

        var tan=Math.tan(ALMath.degToRad(0.5*fov)),
            A=-(zFar+zNear)/(zFar-zNear),
            B=(-2*zFar*zNear)/(zFar-zNear);

        var c = this.components;

        c[ 0 ] = 0.5/tan;       c[ 4 ] = 0;             c[ 8 ] = 0;         c[ 12 ] = 0;
        c[ 1 ] = 0;             c[ 5 ] = (0.5*a/tan);   c[ 9 ] = 0;         c[ 13 ] = 0;
        c[ 2 ] = 0;             c[ 6 ] = 0;             c[ 10 ] = A;        c[ 14 ] = B;
        c[ 3 ] = 0;             c[ 7 ] = 0;             c[ 11 ] =-1;        c[ 15 ] = 0;

        return this;
    },

我的投影矩阵很好。但我的视图矩阵与 gman 库中计算的视图矩阵不同:https://github.com/greggman/twgl.js/blob/master/src/m4.js 在 m4.js 中计算矩阵

c[0] = x.x; c[1] = x.y; c[2] = x.z; c[3] = 0;
c[4] = y.x; c[5] = y.y; c[6] = y.z; c[7] = 0;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; c[11] = 0;
c[12] = eye.x; c[13] = eye.y; c[14] = eye.z; c[15] = 1;

而不是

c[0] = x.x; c[1] = x.y; c[2] = x.z; 
c[4] = y.x; c[5] = y.y; c[6] = y.z; 
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; 
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);

请注意,在我的数学库中,我用轴和眼睛之间的点计算它,如下所述:Calculating a LookAt matrix

所以,那个线程是错误的?我应该直接使用眼睛而不是轴和眼睛之间的点积吗?

如果我运行 gman 发布的脚本,我会得到以下输出:

frustum points
0 -0.414 -0.207 -0.500
1 0.414 -0.207 -0.500
2 -0.414 0.207 -0.500
3 0.414 0.207 -0.500
4 -82.843 -41.421 -100.000
5 82.843 -41.421 -100.000
6 -82.843 41.421 -100.000
7 82.843 41.421 -100.000

camerafrustum points
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.224
6 100.403 93.555 -14.754
7 -16.754 93.555 102.403

screen points (should match width, height)
0 148.858 -47.653 4.029
1 111.806 -38.903 3.734
2 147.454 -72.303 4.217
3 108.845 -59.000 3.876
4 951.911 101.710 9.651
5 61.823 20.354 3.229
6 -833.522 732.104 -10.661
7 25.094 -97.340 4.035

unprojected (should match cameraFrustum points)
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.226
6 100.404 93.556 -14.754
7 -16.754 93.557 102.405

与 gman 发布的结果相同,但在 screen points (should match width, height) 部分中有所不同。

如果我使用以下指令在我的电脑上运行 gman 脚本:<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script> 输出与 gman 发布的相同

screen points (should match width, height)
0 -0.000 -0.000 -1.000
1 300.000 -0.000 -1.000
2 -0.000 150.000 -1.000
3 300.000 150.000 -1.000
4 0.000 0.000 1.000
5 300.000 0.000 1.000
6 -0.000 150.000 1.000
7 300.000 150.000 1.000

但是如果我下载https://twgljs.org/dist/2.x/twgl-full.min.js并存储在html文件的同一目录中并在html文件中使用指令<script src="twgl.js"></script>,输出就像我的数学库,这是:

screen points (should match width, height)
    0 148.858 -47.653 4.029
    1 111.806 -38.903 3.734
    2 147.454 -72.303 4.217
    3 108.845 -59.000 3.876
    4 951.911 101.710 9.651
    5 61.823 20.354 3.229
    6 -833.522 732.104 -10.661
    7 25.094 -97.340 4.035

适用于我的库的脚本如下:

function main()
{

var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar  = 100;
var projection = new ALMath.Matrix4();
projection = projection.perspectiveProjection(45, aspect, zNear, zFar);

var eye = new ALMath.Vector3(1, 2, 3);
var target = new ALMath.Vector3(4, 5, 6);
var up = new ALMath.Vector3(0, 1, 0);

var camera = new ALMath.Matrix4();
camera = camera.lookAt(eye, target, up);
var view = camera.getInverse();
var viewProjection = view.multiply(projection);
var inverseViewProjection = viewProjection.getInverse();

function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {

  var f = 1 / Math.tan(fieldOfView / 2);
  var nearY = zNear / f;
  var nearX = nearY * aspect;
  var farY = zFar / f;
  var farX = farY * aspect;

  return [
    new ALMath.Vector3(-nearX, -nearY, -zNear),
    new ALMath.Vector3( nearX, -nearY, -zNear),
    new ALMath.Vector3(-nearX,  nearY, -zNear),
    new ALMath.Vector3( nearX,  nearY, -zNear),
    new ALMath.Vector3(-farX, -farY, -zFar),
    new ALMath.Vector3( farX, -farY, -zFar),
    new ALMath.Vector3(-farX,  farY, -zFar),
    new ALMath.Vector3( farX,  farY, -zFar),
  ];
}

function projectScreenPoint(width, height, projection, point) {
  var c = projection.transformPoint(point);
  return new ALMath.Vector3((c.x * 0.5 + 0.5) * width,(c.y * 0.5 + 0.5) * height, c.z);

}

function unproject(width, height, inverseViewProjection, p) {
  return inverseViewProjection.transformPoint(new ALMath.Vector3(p.x / width * 2 - 1, p.y / height * 2 - 1, p.z));    
}

function showPoints(label, points) {
  log(label);
  points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}

var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);

var cameraFrustumPoints = frustumPoints.map(
  p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);

var screenPoints = cameraFrustumPoints.map(
  p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);

var unprojectedPoints = screenPoints.map(
  p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
           unprojectedPoints);


function log(...args) {
  var elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}

}

所以现在的问题是:

  • 我应该直接使用 eye 而不是 axis 和 eye 之间的点积吗?
  • 为什么屏幕点不同,从我的库和本地存储的 gman 库以及从

【问题讨论】:

  • 在 3D 中有一整行点在 2D 中具有相同的屏幕坐标。如何选择“正确”的结果?

标签: javascript math 3d


【解决方案1】:

如果我是你,我会写一些测试。你有一个截锥体和一个相机。你应该能够很容易地计算出你的平截头体的角。然后使用这些角,您应该能够投影它们以获得屏幕坐标。然后检查你是否取消投影那些你得到平截头体点的屏幕坐标。

由于你没有发布你的数学库,我将使用我自己的

var m4 = twgl.m4;

// Plug in your math lib here
var m = {
  multiply: (a, b) => m4.multiply(a, b),
  inverse: (a) => m4.inverse(a), 
  identity: () => m4.identity(),
  lookAt: (eye, target, up) => m4.lookAt(eye, target, up),
  perspective: (fov, aspect, zNear, zFar) => m4.perspective(fov, aspect, zNear, zFar),
  transformPoint: (m, p) => m4.transformPoint(m, p),
};

var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar  = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);

var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];

var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);

function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
  
  var f = 1 / Math.tan(fieldOfView / 2);
  var nearY = zNear / f;
  var nearX = nearY * aspect;
  var farY = zFar / f;
  var farX = farY * aspect;

  return [
    [-nearX, -nearY, -zNear],
    [ nearX, -nearY, -zNear],
    [-nearX,  nearY, -zNear],
    [ nearX,  nearY, -zNear],
    [-farX, -farY, -zFar],
    [ farX, -farY, -zFar],
    [-farX,  farY, -zFar],
    [ farX,  farY, -zFar],
  ];
}
    
function projectScreenPoint(width, height, projection, point) {
  var c = m.transformPoint(projection, point);
  return [
    (c[0] * 0.5 + 0.5) * width,
    (c[1] * 0.5 + 0.5) * height,
    c[2],
  ];
}

function unproject(width, height, inverseViewProjection, p) {
  return m.transformPoint(
    inverseViewProjection,
    [
      p[0] / width * 2 - 1,
      p[1] / height * 2 - 1,
      p[2],
    ]);    
}

function showPoints(label, points) {
  log(label);
  points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}

var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);

var cameraFrustumPoints = frustumPoints.map(
  p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);

var screenPoints = cameraFrustumPoints.map(
  p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);

var unprojectedPoints = screenPoints.map(
  p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
           unprojectedPoints);


function log(...args) {
  var elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>

注意:m4.transformPoint 除以w 得到结果

在上面插入你的数学库?

这是一个插入glMatrix的例子

// Plug in your math lib here
var m = {
  multiply: (a, b) => mat4.multiply(mat4.create(), a, b),
  inverse: (a) => mat4.invert(mat4.create(), a), 
  identity: () => mat4.create(),  
  lookAt: (eye, target, up) => mat4.invert(
      mat4.create(), 
      mat4.lookAt(mat4.create(), eye, target, up)),
  perspective: (fov, aspect, zNear, zFar) => mat4.perspective(
      mat4.create(), fov, aspect, zNear, zFar),
  transformPoint: (m, p) => vec3.transformMat4(vec3.create(), p, m),
};

var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar  = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);

var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];

var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);

function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
  
  var f = 1 / Math.tan(fieldOfView / 2);
  var nearY = zNear / f;
  var nearX = nearY * aspect;
  var farY = zFar / f;
  var farX = farY * aspect;

  return [
    [-nearX, -nearY, -zNear],
    [ nearX, -nearY, -zNear],
    [-nearX,  nearY, -zNear],
    [ nearX,  nearY, -zNear],
    [-farX, -farY, -zFar],
    [ farX, -farY, -zFar],
    [-farX,  farY, -zFar],
    [ farX,  farY, -zFar],
  ];
}
    
function projectScreenPoint(width, height, projection, point) {
  var c = m.transformPoint(projection, point);
  return [
    (c[0] * 0.5 + 0.5) * width,
    (c[1] * 0.5 + 0.5) * height,
    c[2],
  ];
}

function unproject(width, height, inverseViewProjection, p) {
  return m.transformPoint(
    inverseViewProjection,
    [
      p[0] / width * 2 - 1,
      p[1] / height * 2 - 1,
      p[2],
    ]);    
}

function showPoints(label, points) {
  log(label);
  points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}

var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);

var cameraFrustumPoints = frustumPoints.map(
  p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);

var screenPoints = cameraFrustumPoints.map(
  p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);

var unprojectedPoints = screenPoints.map(
  p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
           unprojectedPoints);


function log(...args) {
  var elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>

至于您的示例,您将 0 传递给 z,这在截锥体深度的中间某处。我也看到这样的代码

transformedVector(1)/transformedVector(4)

我不知道你的数学库,但我的会是从零开始的索引,所以

transformedVector(0)/transformedVector(3)

这是您添加到示例中的代码。它对我有用。我填写了缺少的数学函数。

const m4 = twgl.m4;

class Vector3 {
  constructor(x, y, z) { 
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0; 
  }  
  sub(v) {
    return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
  }  
  cross(v) {
    return new Vector3(
      this.y * v.z - this.z * v.y,
      this.z * v.x - this.x * v.z,
      this.x * v.y - this.y * v.x);
  }  
  dot(v) {
    return (this.x * v.x) + (this.y * v.y) + (this.z * v.z);
  }
  normalize() {
    var lenSq = this.x * this.x + this.y * this.y + this.z * this.z;
    var len = Math.sqrt(lenSq);
    if (len > 0.00001) {
      return new Vector3(this.x / len, this.y / len, this.z / len);
    } else {
      return new Vector3();
    }
  }
}

class Vector4 {
  constructor(x, y, z, w) { 
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0; 
    this.w = w || 0;
  }
}

class Matrix4 {
  constructor(components) {
    this.components = components || m4.identity();
  }
  multiply(m) {
    return new Matrix4(m4.multiply(m.components, this.components));
  }
  getInverse() {
    return new Matrix4(m4.inverse(this.components));
  }
  multiplyByVector4(v) {
    const m = this.components;
    const x = v.x * m[0 * 4 + 0] + v.y * m[1 * 4 + 0] + v.z * m[2 * 4 + 0] + v.w * m[3 * 4 + 0];
    const y = v.x * m[0 * 4 + 1] + v.y * m[1 * 4 + 1] + v.z * m[2 * 4 + 1] + v.w * m[3 * 4 + 1];
    const z = v.x * m[0 * 4 + 2] + v.y * m[1 * 4 + 2] + v.z * m[2 * 4 + 2] + v.w * m[3 * 4 + 2];
    const w = v.x * m[0 * 4 + 3] + v.y * m[1 * 4 + 3] + v.z * m[2 * 4 + 3] + v.w * m[3 * 4 + 3];
    return new Vector4(x, y, z, w);
  }
  transformPoint(v) {
    const v4 = this.multiplyByVector4(new Vector4(v.x, v.y, v.z, 1));
    return new Vector3(v4.x / v4.w, v4.y / v4.w, v4.z / v4.w);
  }
  lookAt(eye, target, up) {
    var eye = eye || new ALMath.Vector3();
    var up = up || new ALMath.Vector3();
    var target = target || new ALMath.Vector3();

    var c = this.components;

    var z = target.sub(eye);
    z = z.normalize();

    var x = z.cross(up);
    x = x.normalize();

    var y = x.cross(z);
    y = y.normalize();

    c[0] = x.x; c[1] = x.y; c[2] = x.z; 
    c[4] = y.x; c[5] = y.y; c[6] = y.z; 
    c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; 
    c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);

    return this;
  }
  perspectiveProjection( fov, aspect, zNear, zFar ) {
    var a = aspect;

    var tan=Math.tan(ALMath.degToRad(0.5*fov)),
        A=-(zFar+zNear)/(zFar-zNear),
        B=(-2*zFar*zNear)/(zFar-zNear);

    var c = this.components;

    c[ 0 ] = 0.5/tan; c[ 4 ] = 0;           c[ 8 ] = 0;  c[ 12 ] = 0;
    c[ 1 ] = 0;       c[ 5 ] = (0.5*a/tan); c[ 9 ] = 0;  c[ 13 ] = 0;
    c[ 2 ] = 0;       c[ 6 ] = 0;           c[ 10 ] = A; c[ 14 ] = B;
    c[ 3 ] = 0;       c[ 7 ] = 0;           c[ 11 ] =-1; c[ 15 ] = 0;

    return this;
  }
}

class PerspectiveCamera {
  constructor(fieldOfViewDegrees, aspect, zNear, zFar) {
    this.fieldOfViewDegrees = fieldOfViewDegrees || 45;
    this.aspect = aspect || 1;
    this.zNear = zNear || 0.5;
    this.zFar = zFar || 100;

    this.projectionMatrix = new Matrix4();
    this.viewMatrix = new Matrix4();
    this.updateProjection();
  }
  updateProjection() {
    this.projectionMatrix.perspectiveProjection(
      this.fieldOfViewDegrees, this.aspect, this.zNear, this.zFar);
  }
  lookAt(eye, target, up) {
    //this.viewMatrix.lookAt(eye, target, up);
    this.cameraMatrix = this.viewMatrix.getInverse();
  }
  transformPoint(v) {
    // note this tranasforms by the camera matrix 
    // (which is the inverse view matrix)
    // and not the perspective matrix
    return this.cameraMatrix.transformPoint(v);
  }
}

const ALMath = {
  Vector3: Vector3,
  Matrix4: Matrix4,
  degToRad: d => d * Math.PI / 180,
};

const AL3D = {
  width: 300,
  height: 150,
  PerspectiveCamera: PerspectiveCamera,
};

const camera = new AL3D.PerspectiveCamera(40, AL3D.width/AL3D.height);
camera.lookAt(
  new ALMath.Vector3(), 
  new ALMath.Vector3(0,-0.5,-2), 
  new ALMath.Vector3(0,1,0));

function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {

  var f = 1 / Math.tan(ALMath.degToRad(fieldOfView) / 2);
  var nearY = zNear / f;
  var nearX = nearY * aspect;
  var farY = zFar / f;
  var farX = farY * aspect;

  return [
    new ALMath.Vector3(-nearX, -nearY, -zNear),
    new ALMath.Vector3( nearX, -nearY, -zNear),
    new ALMath.Vector3(-nearX,  nearY, -zNear),
    new ALMath.Vector3( nearX,  nearY, -zNear),
    new ALMath.Vector3(-farX, -farY, -zFar),
    new ALMath.Vector3( farX, -farY, -zFar),
    new ALMath.Vector3(-farX,  farY, -zFar),
    new ALMath.Vector3( farX,  farY, -zFar),
  ];
}

const projectionMatrix = camera.projectionMatrix;
const viewMatrix = camera.viewMatrix;
const viewProjection = viewMatrix.multiply(projectionMatrix);
const inverseViewProjection = viewProjection.getInverse();
    
    
    
function projectScreenPoint(width, height, projection, point) {
  var c = projectionMatrix.transformPoint(point);
  return new ALMath.Vector3(
    (c.x * 0.5 + 0.5) * width,
    (c.y * 0.5 + 0.5) * height, 
    c.z);
}
    
function unproject(width, height, inverseViewProjection, p) {
  return inverseViewProjection.transformPoint(new ALMath.Vector3(
    p.x / width * 2 - 1, 
    p.y / height * 2 - 1, 
    p.z));    
}

function showPoints(label, points) {
  log(label);
  points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}

var frustumPoints = getFrustumPoints(camera.fieldOfViewDegrees, camera.aspect, camera.zNear, camera.zFar);
showPoints("frustum points", frustumPoints);

var cameraFrustumPoints = frustumPoints.map(
  p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);

var screenPoints = cameraFrustumPoints.map(
  p => projectScreenPoint(AL3D.width, AL3D.height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);

var unprojectedPoints = screenPoints.map(
  p => unproject(AL3D.width, AL3D.height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
           unprojectedPoints);


function log(...args) {
  var elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
pre { margin: 0; }
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>

【讨论】:

  • 我编辑了我的问题以添加测试输出并对其进行评论。简而言之,看起来视图矩阵是错误的,但我将它基于链接在相关编辑部分的代码中,如果我使用 <script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script> 并且如果我下载它并存储在同一个 html 文件中,我也会得到不同的输出链接:<script src="twgl.js"></script>。顺便说一句,您引用的基于索引的索引是octave 代码,而不是来自我的数学库。在问题中,逐步使用octave 程序完成,以避免我的数学库可能出现的问题
  • 感谢其他访问者提供如此好的答案。
猜你喜欢
  • 2016-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-17
  • 1970-01-01
  • 2021-01-04
相关资源
最近更新 更多