我有一个场景,我想将透视对象(即,距离较远时看起来较小的对象)与正射影像对象(即,与距离无关而大小相同的对象)组合在一起。透 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能够解释为什么正交摄影机不能按预期方式工作和/或为该问题提供解决方案,我仍然很高兴。
最佳答案
您非常接近预期的结果。您忘记了更新相机矩阵,必须对这些矩阵进行计算,以便操作project
和project
可以正常工作:
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 (眼睛)空间。在视口(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)。
在正交投影中,眼睛空间中的坐标被线性映射到归一化的设备坐标。
正投影
在正交投影中,眼睛空间中的坐标被线性映射到归一化的设备坐标。
正射投影矩阵:
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)
透视投影
在“透视投影”中,投影矩阵描述了从针孔相机看到的世界3D点到视口(viewport)的2D点的映射。摄像机视锥中的眼睛空间坐标(截断的金字塔)映射到立方体(规范化的设备坐标)。
透视投影
透视投影矩阵:
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
有关堆栈溢出问题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>