像这样更改函数:
function visibleBox(z) {
var t = Math.tan( THREE.Math.degToRad( camera.fov ) / 2 )
var h = t * 2 * z;
var w = h * camera.aspect;
return new THREE.Box2(new THREE.Vector2(-w, h), new THREE.Vector2(w, -h));
}
并像这样设置圆位置:
circle.position.z = _.random(-camera.near, -camera.far);
var visBox = visibleBox(circle.position.z)
circle.position.x = _.random(visBox.min.x, visBox.max.x);
circle.position.y = visBox.min.y;
代码演示:
let renderer, scene, light, circles, camera;
initialize();
animate();
function initialize() {
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
light = new THREE.AmbientLight();
scene.add(light);
circles = new THREE.Group();
scene.add(circles);
camera = new THREE.PerspectiveCamera(45, renderer.domElement.clientWidth / renderer.domElement.clientHeight, 1);
camera.position.z = circles.position.z + 500;
}
function animate() {
// Update each circle.
Array.from(circles.children).forEach(circle => {
if (circle.position.y < visibleBox(circle.position.z).max.y) {
circle.position.y += 4;
} else {
circles.remove(circle);
}
});
// Create a new circle.
let circle = new THREE.Mesh();
circle.geometry = new THREE.CircleGeometry(30, 30);
circle.material = new THREE.MeshToonMaterial({ color: randomColor(), transparent: true, opacity: 0.5 });
circle.position.z = _.random(-(camera.near+(camera.far-camera.near)/5), -camera.far);
var visBox = visibleBox(circle.position.z)
circle.position.x = _.random(visBox.min.x, visBox.max.x);
circle.position.y = visBox.min.y;
circles.add(circle);
// Render the scene.
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function visibleBox(z) {
var t = Math.tan( THREE.Math.degToRad( camera.fov ) / 2 )
var h = t * 2 * z;
var w = h * camera.aspect;
return new THREE.Box2(new THREE.Vector2(-w, h), new THREE.Vector2(w, -h));
}
function randomColor() {
return `#${ _.sampleSize("abcdef0123456789", 6).join("")}`;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js">
</script>
解释
投影矩阵描述了从场景的 3D 点到视口的 2D 点的映射。它从眼睛空间变换到剪辑空间,并且剪辑空间中的坐标除以w
剪辑坐标的组成部分。 NDC 的范围为 (-1,-1,-1) 到 (1,1,1)。
在透视投影中,深度值和到相机的 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 分量和深度之间的关系:
z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
depth = (z_ndc + 1.0) / 2.0
反向操作如下所示:
n = near, f = far
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)
See 如何在现代 OpenGL 中使用片段着色器中的 gl_FragCoord.z 线性渲染深度? https://stackoverflow.com/questions/7777913/how-to-render-depth-linearly-in-modern-opengl-with-gl-fragcoord-z-in-fragment-sh/45710371#45710371
视图空间中的投影面积与视图空间的 Z 坐标之间是线性关系。这取决于视角和纵横比。
标准化设备大小可以转换为视图空间中的大小,如下所示:
aspect = w / h
tanFov = tan( fov_y * 0.5 );
size_x = ndx_size_x * (tanFov * aspect) * z_eye;
size_y = ndx_size_y * tanFov * z_eye;
如果透视投影矩阵已知并且投影是对称的(视线位于视口的中心并且视野没有移位),则可以按如下方式完成:
size_x = ndx_size_x * / (prj_mat[0][0] * z_eye);
size_y = ndx_size_y * / (prj_mat[1][1] * z_eye);
See 视野 + 纵横比 + 投影矩阵的视图矩阵(HMD OST 校准) https://stackoverflow.com/questions/46182845/field-of-view-aspect-ratio-view-matrix-from-projection-matrix-hmd-ost-calib/46195462#46195462
请注意,标准化设备坐标中的每个位置都可以通过逆投影矩阵转换为视图空间坐标:
mat4 inversePrjMat = inverse( prjMat );
vec4 viewPosH = inversePrjMat * vec3( ndc_x, ndc_y, 2.0 * depth - 1.0, 1.0 );
vec3 viewPos = viewPos.xyz / viewPos.w;
See 如何在给定视图空间深度值和 ndc xy 的情况下恢复视图空间位置 https://stackoverflow.com/questions/11277501/how-to-recover-view-space-position-given-view-space-depth-value-and-ndc-xy/46118945#46118945
这意味着具有特定深度的未投影矩形可以如下计算:
vec4 viewLowerLeftH = inversePrjMat * vec3( -1.0, -1.0, 2.0 * depth - 1.0, 1.0 );
vec4 viewUpperRightH = inversePrjMat * vec3( 1.0, 1.0, 2.0 * depth - 1.0, 1.0 );
vec3 viewLowerLeft = viewLowerLeftH.xyz / viewLowerLeftH.w;
vec3 viewUpperRight = viewUpperRightH.xyz / viewUpperRightH.w;