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

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

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

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

las,这并不符合预期。正交对象始终在透视对象之前进行渲染,即使它们应位于透视对象之间。考虑以下示例,在该示例中,蓝色圆圈应显示在红色正方形的后面,但应显示在绿色正方形的前面(不是):
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能够解释为什么正交摄影机不能按预期方式工作和/或为该问题提供解决方案,我仍然很高兴。

最佳答案

您非常接近预期的结果。您忘记了更新相机矩阵,必须对这些矩阵进行计算,以便操作projectproject可以正常工作:

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

说明:

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

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

    通常,深度值的计算如下:
    float ndc_depth = clip_space_pos.z / clip_space_pos.w;
    float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;
    

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

    在正交投影中,眼睛空间中的坐标被线性映射到归一化的设备坐标。

    正投影

    在正交投影中,眼睛空间中的坐标被线性映射到归一化的设备坐标。

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

    正射投影矩阵:
    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位置从透视图移至正交相机-LMLPHP

    透视投影

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

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

    透视投影矩阵:
    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
    

    在Perspective Projection中,Z分量由有理函数计算:
    z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
    

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

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

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

    可以通过 project THREE.PerspectiveCamera函数轻松地计算出标准化的设备坐标。 project从世界空间转换为 View 空间,从 View 空间转换为规范化的设备坐标。

    为了找到正投影之间的Z坐标,必须将中间标准化设备Z坐标转换为 View 空间Z坐标。这可以通过 unproject THREE.PerspectiveCamera函数完成。 unproject从规范化的设备坐标转换为 View 空间,并从 View 空间转换为世界空间。

    进一步查看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>

    09-10 10:21
    查看更多