javascript-在three.js中将Z位置从透视图移到正交相机

我有一个场景,我想将透视图对象(即,距离较远的对象看起来较小)和正射影像对象(即,与距离无关而大小相同的对象)组合在一起.透视图对象是渲染的“世界”的一部分,而正交图对象是装饰物,如标签或图标.与HUD不同,我希望将正投影对象“渲染”在世界内部,这意味着它们可以被世界对象覆盖(想象一下在标签之前经过的平面).

我的解决方案是使用一个渲染器,但使用两个场景,一个场景使用PerspectiveCamera,一个场景使用OrthogographicCamera.我按顺序渲染它们而不清除z缓冲区(渲染器的autoClear属性设置为false).我面临的问题是,我需要同步每个场景中对象的位置,以便为一个场景中的对象分配一个z位置,该位置位于另一个场景中位于该场景之前,但位于该场景之前的对象之后在它后面.

为此,我将透视图场景指定为“领先”场景,即.根据该场景分配所有对象(透视图和正交图)的所有坐标.透视图对象直接使用这些坐标,并在该场景内以及使用透视图相机进行渲染.将正射影像对象的坐标转换为正射影像场景中的坐标,然后使用正射影像相机在该场景中进行渲染.我通过将透视场景中的坐标投影到透视相机的视图窗格,然后使用正交摄影机返回正交场景来进行转换:

position.project(perspectiveCamera).unproject(orthogographicCamera);

,这并非按预期运行.正交对象始终在透视对象之前进行渲染,即使它们应位于透视对象之间.考虑以下示例,在该示例中,蓝色圆圈应显示在红色正方形的后面,但应显示在绿色正方形的前面(不是):

var pScene = new THREE.Scene();
var oScene = new THREE.Scene();

var pCam = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
pCam.position.set(0, 40, 50);
pCam.lookAt(new THREE.Vector3(0, 0, -50));

var oCam = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 500);
oCam.Position = pCam.position.clone();

pScene.add(pCam);
pScene.add(new THREE.AmbientLight(0xFFFFFF));

oScene.add(oCam);
oScene.add(new THREE.AmbientLight(0xFFFFFF));

var frontPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x990000 }));
frontPlane.position.z = -50;
pScene.add(frontPlane);

var backPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x009900 }));
backPlane.position.z = -100;
pScene.add(backPlane);

var circle = new THREE.Mesh(new THREE.CircleGeometry(60, 20), new THREE.MeshBasicMaterial( { color: 0x000099 }));
circle.position.z = -75;

//Transform position from perspective camera to orthogonal camera -> doesn't work, the circle is displayed in front
circle.position.project(pCam).unproject(oCam);

oScene.add(circle);

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

renderer.autoClear = false;
renderer.render(oScene, oCam);
renderer.render(pScene, pCam);

您可以try out the code here.

在透视世界中,圆的(世界)z位置是-75,在正方形之间(-50和-100).但是它实际上显示在两个正方形的前面.如果您手动将圆形的z位置(在正交场景中)设置为-500,则它会显示在正方形之间,因此,如果位置正确,则我尝试的方法原则上应该可行.

我知道我无法使用正交摄影机和透视相机渲染场景.我的意图是在每次渲染之前重新定位所有正交物体,以使其看起来在正确的位置.

我该怎么做才能从透视坐标中计算出正交坐标,以便用正确的深度值渲染对象?

更新:

如果有人遇到类似问题,我已经用当前解决方案添加了答案.但是,由于此解决方案无法提供与正交摄影机相同的质量.因此,如果somoeone能够解释为什么正交摄影机不能按预期方式工作和/或为该问题提供解决方案,我仍然很高兴.

解决方法:

您非常接近预期的结果.您忘记了更新相机矩阵,必须计算这些矩阵以确保操作项目和项目可以正常工作:

pCam.updateMatrixWorld ( false );
oCam.updateMatrixWorld ( false );
circle.position.project(pCam).unproject(oCam);

说明:

在渲染中,通常通过模型矩阵,视图矩阵和投影矩阵来变换场景的每个网格.

>投影矩阵:
投影矩阵描述了从场景的3D点到视口的2D点的映射.投影矩阵从视图空间转换为剪辑空间,并将剪辑空间中的坐标转换为范围为(-1,-1,-1)至(1、1、1)的归一化设备坐标(NDC)通过除以剪辑坐标的w分量.
>查看矩阵:
视图矩阵描述了从中查看场景的方向和位置.视图矩阵从世界空间转换为视图(眼睛)空间.在视口上的坐标系统中,X轴指向左侧,Y轴指向上方,Z轴指向视图之外(请注意,在右手系统中,Z轴是X-轴的叉积轴和Y轴).
>模型矩阵:
模型矩阵定义场景中网格的位置,方向和相对大小.模型矩阵将顶点位置从网格转换为世界空间.

如果在另一个片段的“后面”或“之前”绘制一个片段,则取决于该片段的深度值.对于正交投影,视图空间的Z坐标线性映射到深度值,而在透视投影中,它不是线性的.

通常,深度值的计算如下:

float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;

投影矩阵描述了从场景的3D点到视口的2D点的映射.它从眼睛空间转换到剪辑空间,并且通过除以剪辑坐标的w分量,将剪辑空间中的坐标转换为归一化设备坐标(NDC).

在正交投影中,眼睛空间中的坐标线性映射到标准化设备坐标.

正投影

在正交投影中,眼睛空间中的坐标线性映射到标准化设备坐标.

javascript-在three.js中将Z位置从透视图移到正交相机

正射投影矩阵:

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

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

在正交投影中,Z分量由线性函数计算:

z_ndc = z_eye * -2/(f-n) - (f+n)/(f-n)

javascript-在three.js中将Z位置从透视图移到正交相机

透视投影

在“透视投影”中,投影矩阵描述了从针孔相机看到的世界3D点到视口的2D点的映射.摄像机视锥中的眼睛空间坐标(截断的金字塔)映射到立方体(标准化的设备坐标).

透视投影
javascript-在three.js中将Z位置从透视图移到正交相机

透视投影矩阵:

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

在“透视投影”中,Z分量由有理函数计算:

z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye

javascript-在three.js中将Z位置从透视图移到正交相机

有关堆栈溢出问题How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?的答案,请参见详细说明.

在您的情况下,这意味着您必须以这种方式在正投影中选择圆的Z坐标,并且深度值介于透视投影中的对象深度之间.
由于在两种情况下深度值都不是depth = z ndc * 0.5 0.5,因此也可以通过归一化的设备坐标而不是深度值来进行计算.

可以通过THREE.PerspectiveCamera的投影功能轻松地计算出标准化的设备坐标.该项目从世界空间过渡到视图空间,从视图空间过渡到标准化设备坐标.

为了找到正投影中介于两者之间的Z坐标,必须将中间标准化设备Z坐标转换为视图空间Z坐标.这可以通过THREE.PerspectiveCamera的取消投影功能完成.取消投影将从规范化的设备坐标转换为视图空间,并从视图空间转换为世界空间.

进一步参见OpenGL – Mouse coordinates to Space coordinates.

参见示例:

var renderer, pScene, oScene, pCam, oCam, frontPlane, backPlane, circle;

  var init = function () {
    pScene = new THREE.Scene();
    oScene = new THREE.Scene();
    
    pCam = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
    pCam.position.set(0, 40, 50);
    pCam.lookAt(new THREE.Vector3(0, 0, -50));
    
    oCam = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, 1, 500);
    oCam.Position = pCam.position.clone();
    
    pScene.add(pCam);
    pScene.add(new THREE.AmbientLight(0xFFFFFF));
    
    oScene.add(oCam);
    oScene.add(new THREE.AmbientLight(0xFFFFFF));
    
    
    frontPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x990000 }));
    frontPlane.position.z = -50;
    pScene.add(frontPlane);
    
    backPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x009900 }));
    backPlane.position.z = -100;
    pScene.add(backPlane);

    circle = new THREE.Mesh(new THREE.CircleGeometry(20, 20), new THREE.MeshBasicMaterial( { color: 0x000099 }));
    circle.position.z = -75;

    
    //Transform position from perspective camera to orthogonal camera -> doesn't work, the circle is displayed in front
    pCam.updateMatrixWorld ( false );
    oCam.updateMatrixWorld ( false );
    circle.position.project(pCam).unproject(oCam);
    
    oScene.add(circle);
    
    renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
  };
  
  var render = function () {
  
    renderer.autoClear = false;
    renderer.render(oScene, oCam);
    renderer.render(pScene, pCam);
  };
  
  var animate = function () {
      requestAnimationFrame(animate);
      //controls.update();
      render();
  };
  
  
  init();
  animate();
html,body {
    height: 100%;
    width: 100%;
    margin: 0;
    overflow: hidden;
}
<script src="https://threejs.org/build/three.min.js"></script>
上一篇:利用beef盗取浏览器cookie,并实现页面跳转


下一篇:存储型XSS的利用