如何从VRFrameData计算FOV?

时间:2018-01-25 13:10:20

标签: javascript webgl webvr

以前在VREyeParameters中有视野信息,但已弃用。所以现在我想知道:可以使用VRFrameData提供的视图/投影矩阵来计算吗?

2 个答案:

答案 0 :(得分:5)

投影矩阵描述了从场景的3D点到视口的2D点的映射。投影矩阵从视图空间变换到剪辑空间。剪辑空间坐标为Homogeneous coordinates。通过除以剪辑的w分量,剪辑空间中的坐标转换为范围(-1,-1,-1)到(1,1,1)范围内的规范化设备坐标(NDC)坐标。

在Perspective Projection中,投影矩阵描述了从针孔相机到视口的2D点看世界中3D点的映射。
相机平截头体(截头金字塔)中的眼睛空间坐标被映射到立方体(标准化设备坐标)。

enter image description here

如果你想知道视图空间中相机平截头体的角,那么你必须转换标准化设备空间的角(-1,-1,-1),...,(1,1, 1)通过反投影矩阵。要获得cartesian coordinates,结果的X,Y和Z分量必须除以结果的W(第四)分量。
glMatrix是一个提供矩阵运算和数据类型的库,例如mat4vec4

projection  = mat4.clone( VRFrameData.leftProjectionMatrix );
inverse_prj = mat4.create();
mat4.invert( inverse_prj, projection );

pt_ndc = [-1, -1, -1];
v4_ndc = vec4.fromValues( pt_ndc[0], pt_ndc[1], pt_ndc[2], 1 );

v4_view = vec4.create();  
vec4.transformMat4( v4_view, v4_ndc, inverse_prj );
pt_view = [v4_view[0]/v4_view[3], v4_view[1]/v4_view[3], v4_view[2]/v4_view[3]]; 

转换视图坐标到世界坐标可以通过逆视图矩阵完成。

view         = mat4.clone( VRFrameData.leftViewMatrix );
inverse_view = mat4.create();
mat4.invert( inverse_view, view );

v3_view  = vec3.clone( pt_view );
v3_world = vec3.create();
mat4.transformMat4( v3_world, v3_view, inverse_view );

注意,左右投影矩阵不对称。这意味着视线不在视锥体的中心,左眼和右眼的视线不同。


另外请注意,透视投影矩阵如下所示:

r = right, l = left, b = bottom, t = top, n = near, f = far

2*n/(r-l)      0              0                0
0              2*n/(t-b)      0                0
(r+l)/(r-l)    (t+b)/(t-b)    -(f+n)/(f-n)    -1    
0              0              -2*f*n/(f-n)     0

其中:

a = w / h
ta = tan( fov_y / 2 );

2 * n / (r-l) = 1 / (ta * a)
2 * n / (t-b) = 1 / ta

如果投影是对称的,其中视线位于视口的中心并且视野不会移位,则矩阵可以简化:

1/(ta*a)  0     0              0
0         1/ta  0              0
0         0    -(f+n)/(f-n)   -1    
0         0    -2*f*n/(f-n)    0

这意味着视角可以通过以下方式计算:

fov_y = Math.atan( 1/prjMat[5] ) * 2; // prjMat[5] is prjMat[1][1] 

和纵横比:

aspect = prjMat[5] / prjMat[0];


如果投影矩阵仅沿水平方向对称,则视场角的计算也有效。这意味着-bottom等于top。对于2只眼睛的投影矩阵,情况就是这样。

此外:

z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));

通过替换投影矩阵的字段,这是:

A = prj_mat[2][2]
B = prj_mat[3][2]
z_eye = B / (A + z_ndc)

这意味着到近平面和远平面的距离可以通过以下公式计算:

A = prj_mat[10]; // prj_mat[10] is prj_mat[2][2]
B = prj_mat[14]; // prj_mat[14] is prj_mat[3][2]

near = - B / (A - 1);
far  = - B / (A + 1);

答案 1 :(得分:2)

SOHCAHTOA宣布"所以"," cah"," toe-ah"

  • SOH - >正弦(角度)=与斜边相反
  • CAH - >余弦(角度)=与斜边相邻
  • TOA - >切线(角度)=相邻
  • 对面

enter image description here

告诉我们三角形的各个方面与各种三角函数之间的关系

因此,观察平截头体图像,我们可以从眼睛到近平面到平截头体顶部采用直角三角形来计算视场的切线,我们可以使用反正切将切线转回一个角度。

enter image description here

因为我们知道投影矩阵的结果占据了我们的世界空间平截头体并将其转换为剪辑空间并最终转换为规范化设备空间(-1,-1,-1)到(+ 1,+ 1,+ 1)我们可以通过将NDC空间中的对应点乘以投影矩阵的倒数来得到我们需要的位置

eye = 0,0,0
centerAtNearPlane = inverseProjectionMatrix * (0,0,-1)
topCenterAtNearPlane = inverseProjectionMatrix * (0, 1, -1)

然后

opposite = topCenterAtNearPlane.y
adjacent = -centerAtNearPlane.z
halfFieldOfView = Math.atan2(opposite, adjacent)
fieldOfView = halfFieldOfView * 2

让我们进行测试



const m4 = twgl.m4;
const fovValueElem = document.querySelector("#fovValue");
const resultElem = document.querySelector("#result");
let fov = degToRad(45);

function updateFOV() {
  fovValueElem.textContent = radToDeg(fov).toFixed(1);
  
  // get a projection matrix from somewhere (like VR)
  const projection = getProjectionMatrix();
  
  // now that we have projection matrix recompute the FOV from it
  const inverseProjection = m4.inverse(projection);
  const centerAtZNear = m4.transformPoint(inverseProjection, [0, 0, -1]);
  const topCenterAtZNear = m4.transformPoint(inverseProjection, [0, 1, -1]);
  
  const opposite = topCenterAtZNear[1];
  const adjacent = -centerAtZNear[2];
  const halfFieldOfView = Math.atan2(opposite, adjacent);
  const fieldOfView = halfFieldOfView * 2;
  
  resultElem.textContent = radToDeg(fieldOfView).toFixed(1);
}

updateFOV();

function getProjectionMatrix() {
  // doesn't matter. We just want a projection matrix as though
  // someone else made it for us.
  const aspect = 2 / 1; 
  // choose some zNear and zFar
  const zNear = .5;
  const zFar = 100;
  return m4.perspective(fov, aspect, zNear, zFar);
}


function radToDeg(rad) {
  return rad / Math.PI * 180;
}

function degToRad(deg) {
  return deg / 180 * Math.PI;
}

document.querySelector("input").addEventListener('input', (e) => {
  fov = degToRad(parseInt(e.target.value));
  updateFOV();
});

<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<input id="fov" type="range" min="1" max="179" value="45"><label>fov: <span id="fovValue"></span></label>
<div>computed fov: <span id="result"></span></div>
&#13;
&#13;
&#13;

请注意,这假设平截头体的中心直接位于眼前。如果不是,那么您可能需要通过计算从眼睛到中心的矢量长度来计算adjacent

const v3 = twgl.v3;

...

const adjacent = v3.length(centerAtZNear);