Это общая проблема WebGL, но для ясности я буду использовать три. js, чтобы продемонстрировать мою проблему здесь.
Допустим, у меня есть самолет и перспективная камера. Я пытаюсь получить ограничивающий прямоугольник плоскости относительно окна / окна просмотра. Вот как я это делаю до сих пор:
- Сначала получите модельViewProjectionMatrix, умножив матрицу projectionMatrix камеры на плоскую матрицу.
- Примените эту модельViewProjectionMatrix к вершинам плоскости 4 углов .
- Получить минимальное / максимальное значения результата и преобразовать их обратно в координаты области просмотра.
Работает хорошо, пока плоскость не обрезается камерой рядом с плоскостью (обычно при использовании большое поле зрения), портя мои результаты.
Можно ли как-нибудь получить правильные значения, даже если камера рядом с плоскостью обрезает части моей плоскости? Может быть, получая пересечение между плоскостью и камерой рядом с плоскостью?
Редактировать: Одна идея, о которой я могу подумать, состоит в том, чтобы получить два нормализованных вектора v1 и v2, как показано на этой схеме: пересечения между плоскость и камера вблизи плоскости схемы . Затем я должен был бы получить длину этих векторов, чтобы они go от угла плоскости до точки пересечения (зная положение ближней плоскости Z), но я все еще борюсь за эту последнюю часть.
В любом случае, вот код 3. js и соответствующий jsfiddle (раскомментируйте строку 109 для отображения координат ошибки): https://jsfiddle.net/fbao9jp7/1/
let scene = new THREE.Scene();
let ww = window.innerWidth;
let wh = window.innerHeight;
// camera
const nearPlane = 0.1;
const farPlane = 200;
let camera = new THREE.PerspectiveCamera(45, ww / wh, nearPlane, farPlane);
scene.add(camera);
// renderer
let renderer = new THREE.WebGLRenderer();
renderer.setSize(ww, wh);
document.getElementById("canvas").appendChild(renderer.domElement);
// basic plane
let plane = new THREE.Mesh(
new THREE.PlaneGeometry(0.75, 0.5),
new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load('https://source.unsplash.com/EqFjlsOZULo/1280x720'),
side: THREE.DoubleSide,
})
);
scene.add(plane);
function displayBoundingRectangle() {
camera.updateProjectionMatrix();
// keep the plane at a constant position along Z axis based on camera FOV
plane.position.z = -1 / (Math.tan((Math.PI / 180) * 0.5 * camera.fov) * 2.0);
plane.updateMatrix();
// get the plane model view projection matrix
let modelViewProjectionMatrix = new THREE.Matrix4();
modelViewProjectionMatrix = modelViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, plane.matrix);
let vertices = plane.geometry.vertices;
// apply modelViewProjectionMatrix to our 4 vertices
let projectedPoints = [];
for (let i = 0; i < vertices.length; i++) {
projectedPoints.push(vertices[i].applyMatrix4(modelViewProjectionMatrix));
}
// get our min/max values
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (let i = 0; i < projectedPoints.length; i++) {
let corner = projectedPoints[i];
if (corner.x < minX) {
minX = corner.x;
}
if (corner.x > maxX) {
maxX = corner.x;
}
if (corner.y < minY) {
minY = corner.y;
}
if (corner.y > maxY) {
maxY = corner.y;
}
}
// we have our four coordinates
let worldBoundingRect = {
top: maxY,
right: maxX,
bottom: minY,
left: minX,
};
// convert coordinates from [-1, 1] to [0, 1]
let screenBoundingRect = {
top: 1 - (worldBoundingRect.top + 1) / 2,
right: (worldBoundingRect.right + 1) / 2,
bottom: 1 - (worldBoundingRect.bottom + 1) / 2,
left: (worldBoundingRect.left + 1) / 2,
};
// add width and height
screenBoundingRect.width = screenBoundingRect.right - screenBoundingRect.left;
screenBoundingRect.height = screenBoundingRect.bottom - screenBoundingRect.top;
var boundingRectEl = document.getElementById("plane-bounding-rectangle");
// apply to our bounding rectangle div using window width and height
boundingRectEl.style.top = screenBoundingRect.top * wh + "px";
boundingRectEl.style.left = screenBoundingRect.left * ww + "px";
boundingRectEl.style.height = screenBoundingRect.height * wh + "px";
boundingRectEl.style.width = screenBoundingRect.width * ww + "px";
}
// rotate the plane
plane.rotation.x = -2;
plane.rotation.y = -0.8;
/* UNCOMMENT THIS LINE TO SHOW HOW NEAR PLANE CLIPPING AFFECTS OUR BOUNDING RECTANGLE VALUES */
//camera.fov = 150;
// render scene
render();
// show our bounding rectangle
displayBoundingRectangle();
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
body {
margin: 0;
}
#canvas {
width: 100vw;
height: 100vh;
}
#plane-bounding-rectangle {
position: fixed;
pointer-events: none;
background: red;
opacity: 0.2;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.min.js"></script>
<div id="canvas"></div>
<div id="plane-bounding-rectangle"></div>
Большое спасибо,