4

I am developing an 3D engine from scratch and I am trying to unproject a vector. I use my own Math library called ALMath.js. As far as I know, to convert 2d screen coordinates into 3d world coordinates I need multiply the vector formed by x and y coordinates in the canvas by the inverse of ViewProjection Matrix. This is the code for 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;
  }

The ALMath library works fine. I use it around all engine (compute the model-view-projection, create projection matrix, do the inverse ...) and works well. In fact I check the result of the operations using Octave (alternative to matlab) and the result is the same with ALMath.

The problem is that if I click in the upper left corner:

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));
         });

with x = 0 and y = 2 I get a vector = (-0.12131, -0.25894, -0.79409) and I know that it is wrong because If I set the cube mesh in that position I see that this is not the upper left corner.

I have programmed a lookAt function in the camera class

lookAt : function (eye, target, up)

As a example I show the operations for x = 0 and y = 2 with octave.

Firs I set my camera as follow:

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));

And this is step by step calculations in octave that match with javascript code result

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

Maybe I am forgeting something, but I don't know. What is wrong in my logic. Thanks.

Edit: The problem seem to be the view matrix. This is the code for my view matrix:

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;
    },

and this is for projection:

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;
    },

My projection matrix is fine. But my view matrix is not the same that the view matrix compute in the gman library: https://github.com/greggman/twgl.js/blob/master/src/m4.js in m4.js the matrix is computed

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;

instead of

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);

Note that in my math library I computed it with dot between axis and eye as said here: Calculating a LookAt matrix

So,that thread is wrong? I should use eye directly instead dot product between axis and eye?

If I run the script posted by gman I get the following ouput:

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

Is the same that result posted by gman but it differs in the screen points (should match width, height) section.

If I run the gman script in my pc with this directive: <script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script> the output is the same that gman posted

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

But If I download https://twgljs.org/dist/2.x/twgl-full.min.js and store in the same directory that html file and use the directive <script src="twgl.js"></script> in the html file, the output is like my Math library, this is:

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

The script adapted to my library is the following:

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);
}

}

So the questions now are:

  • I should use eye directly instead dot product between axis and eye?
  • Why the screen points differs, from my library and local stored gman's library and from the
Community
  • 1
  • 1
RdlP
  • 1,366
  • 5
  • 25
  • 45
  • There is a whole line of points in 3D that have the same screen coordinates in 2D. How do you select the "correct" result? – Lutz Lehmann Dec 24 '16 at 06:55

1 Answers1

5

If I were you I'd write some tests. You have a frustum and a camera. You should easily be able to compute the corners of your frustum. Then using those corners you should be able to project them to get screen coordinates. Then check if you unproject those screen coordinates that you get the frustum points back.

Since you didn't post your math library I'll use my own

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>

Note: m4.transformPoint does the divide by w for the result

Plug your math lib in above?

Here's an example of plugging in 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>

As for your example you're passing on 0 for z which is somewhere in the middle of the frustum depth wise. Also I see code like this

transformedVector(1)/transformedVector(4)

I don't know your math library but mine would be zero based indices so

transformedVector(0)/transformedVector(3)

Here's the code you added to your example. It's working for me. I filled in the missing math functions.

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>
gman
  • 100,619
  • 31
  • 269
  • 393
  • I edited my question to add the test output and comment it. Briefly, Looks like the view matrix were wrong, but I based it in a code that link in the edit section in question and also I get different output if I use `` and if I download it and store in the same html file and link with: ``. By the way the one based indices that you reference is `octave` code and not from my math library. In the question the step by step is done with `octave` program to avoid posible problems of my math library – RdlP Dec 24 '16 at 14:27
  • Thanks for such a nice answer for other visitors. – RE60K Dec 24 '16 at 19:04