我有一个场景,我想将透视对象(即远处看起来较小的物体)与正交对象(即不论距离看起来大小相同的物体)组合在一起。透视对象是渲染的世界"的一部分,而正交对象是装饰品,如标签或图标。与HUD不同,我希望正交对象能够在"内部进行渲染。世界,这意味着它们可以被世界物体覆盖(想象一个在标签之前经过的平面)。
我的解决方案是使用一个渲染器,但有两个场景,一个带有PerspectiveCamera
,另一个带有OrthogographicCamera
。我按顺序渲染它们而不清除z缓冲区(渲染器的autoClear
属性设置为false
)。我面临的问题是我需要同步每个场景中对象的位置,以便为一个场景中的对象分配一个z位置,该位置位于另一个场景中的对象后面在它之前,但在它背后的物体之前。
要做到这一点,我将我的视角场景指定为"领先"场景,即。基于该场景分配所有对象的所有坐标(透视和正交)。透视对象直接使用这些坐标,并使用透视摄影机在该场景内渲染。将正交对象的坐标变换为正交场景中的坐标,然后利用正射摄像机在该场景中进行渲染。我通过将透视场景中的坐标投影到透视摄像机的视图窗格然后使用正交摄像机返回到正交场景来进行转换:
position.project(perspectiveCamera).unproject(orthogographicCamera);
唉,这不是有效的。正交对象始终在透视对象之前呈现,即使它们应位于它们之间。考虑这个例子,其中蓝色圆圈应该显示在红色正方形后面,但之前绿色正方形(它不是' t):
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);
在透视世界中,圆的(世界)z位置是-75,它位于正方形(-50和-100)之间。但它实际上显示在两个正方形的前面。如果手动将圆圈z位置(在正交场景中)设置为-500,则会在正方形之间显示,因此在正确定位的情况下,原则上我应该尝试的是什么。
我知道我无法使用正射和透视相机渲染场景。我的目的是在每次渲染之前重新定位所有正交对象,使它们出现以处于正确的位置。
我需要做什么才能从透视坐标计算正交坐标,以便使用正确的深度值渲染对象?
更新
如果有人遇到类似的问题,我已经在问题的当前解决方案中添加了答案。然而,由于该解决方案不能提供与正射相机相同的质量。所以如果索索能够解释为什么正交摄像机不能按预期工作和/或提供问题的解决方案,我仍然会很高兴。
答案 0 :(得分:2)
你非常接近你所期望的结果。您忘记更新相机矩阵,必须计算操作project
和project
才能正常工作:
pCam.updateMatrixWorld ( false );
oCam.updateMatrixWorld ( false );
circle.position.project(pCam).unproject(oCam);
在渲染中,场景的每个网格通常由模型矩阵,视图矩阵和投影矩阵进行变换。
投影矩阵:
投影矩阵描述了从场景的3D点到视口的2D点的映射。投影矩阵从视图空间转换到剪辑空间,剪辑空间中的坐标转换为范围(-1,-1,-1)到(1,1,1)范围内的规范化设备坐标(NDC)通过用剪辑坐标的w分量划分。
查看矩阵:
视图矩阵描述了查看场景的方向和位置。视图矩阵从wolrd空间转换为视图(眼睛)空间。在视口的坐标系中,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)。
在Orthographic Projection中,眼睛空间中的坐标线性映射到标准化设备坐标。
在Orthographic Projection中,眼睛空间中的坐标线性映射到标准化设备坐标。
正交投影矩阵:
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)
在Perspective Projection中,投影矩阵描述了从针孔相机到视口的2D点看世界中3D点的映射。
相机平截头体(截头金字塔)中的眼睛空间坐标被映射到立方体(标准化设备坐标)。
透视投影矩阵:
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
请参阅Stack Overflow问题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
的project
函数轻松计算。 project
从wolrd空间转换到视图空间,从视图空间转换为规范化设备坐标。
为了找到正交投影之间的Z坐标,必须将中间标准化设备Z坐标变换为视图空间Z坐标。这可以通过THREE.PerspectiveCamera
的unproject
函数完成。 unproject
从标准化设备坐标转换为视图空间,从视图空间转换为世界sapce。
进一步查看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>
&#13;
答案 1 :(得分:1)
我找到了一个只涉及透视相机的解决方案,并根据它们与相机的距离来缩放装饰。它类似于answer posted to a similar question,但不完全相同。我的具体问题是,我不仅要求装饰的大小与他们与相机的距离无关,我还需要在屏幕上控制的确切大小。
要将它们缩放到正确的大小,而不是任何不会更改的大小,我使用该函数计算屏幕大小found in this answer来计算已知屏幕长度的矢量两端的位置,并检查投影到屏幕的长度。根据长度差异,我可以计算出精确的比例因子:
var widthVector = new THREE.Vector3( 100, 0, 0 );
widthVector.applyEuler(pCam.rotation);
var baseX = getScreenPosition(circle, pCam).x;
circle.position.add(widthVector);
var referenceX = getScreenPosition(circle, pCam).x;
circle.position.sub(widthVector);
var scale = 100 / (referenceX - baseX);
circle.scale.set(scale, scale, scale);
此解决方案的问题在于,在大多数情况下,计算精确到足以提供精确的大小。但是,偶尔出现一些舍入错误会使装饰无法正确呈现。