Три. js ограничивающего прямоугольника плоскости относительно области просмотра и ближней плоскости - PullRequest
0 голосов
/ 19 апреля 2020

Это общая проблема 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>

Большое спасибо,

Ответы [ 2 ]

0 голосов
/ 20 апреля 2020

Исходя из моей схемы в начальном вопросе, мне удалось решить мою проблему.

Я не буду публиковать здесь весь фрагмент, потому что он довольно длинный и многословный (и он также выглядит как грязный хак), но это основная идея:

  • Получить обрезанные и неотрезанные углы.

  • Использовать координаты неотрезанных углов и действительно маленький вектор, идущий от этого угла к отсеченному и рекурсивно добавляющий его к нашей неотрезанной угловой координате, пока мы не достигнем положения ближней плоскости Z: мы нашли пересечение с ближней плоскостью.

Допустим, верхний левый угол плоскости не обрезается, а нижний левый угол обрезается. Чтобы найти пересечение между камерой вблизи плоскости и левой стороной плоскости, мы сделаем что-то вроде этого:

// find the intersection by adding a vector starting from a corner till we reach the near plane
function getIntersection(refPoint, secondPoint) {
    // direction vector to add
    let vector = secondPoint.sub(refPoint);
    // copy our corner refpoint
    var intersection = refPoint.clone();
    // iterate till we reach near plane
    while(intersection.z > -1) {
        intersection.add(vector);
    }

    return intersection;
}

// get our top left corner projected coordinates
let topLeftCorner = vertices[0].applyMatrix4(modelViewProjectionMatrix);

// get a vector parallel to our left plane side toward the bottom left corner and project its coordinates as well
let directionVector = vertices[0].clone().sub(new THREE.Vector3(0, -0.05, 0)).applyMatrix4(modelViewProjectionMatrix);

// get the intersection with the near plane
let bottomLeftIntersection = getIntersection(topLeftCorner, directionVector);

Я уверен, что был бы более аналитический подход для решения этой проблемы, но это работает так что я буду придерживаться этого пока.

0 голосов
/ 19 апреля 2020

Показывает ли это проблему?

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;
  
  // get our min/max values
  let minX = Infinity;
  let maxX = -Infinity;

  let minY = Infinity;
  let maxY = -Infinity;

  // apply modelViewProjectionMatrix to our 4 vertices
  let corner = new THREE.Vector3();
  for (let i = 0; i < vertices.length; i++) {
    corner.copy(vertices[i]);
    corner.applyMatrix4(modelViewProjectionMatrix);

    minX = Math.min(corner.x, minX);
    maxX = Math.max(corner.x, maxX);
    minY = Math.min(corner.y, minY);
    maxY = Math.max(corner.y, maxY);
  }

  // we have our four coordinates
  let worldBoundingRect = {
    top: maxY,
    right: maxX,
    bottom: minY,
    left: minX,
  };
  document.querySelector('#info').textContent = `${minX.toFixed(2)}, ${maxX.toFixed(2)}, ${minY.toFixed(2)}, ${minY.toFixed(2)}`;

  // 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();


function render(time) {
  camera.fov = THREE.MathUtils.lerp(45, 150, Math.sin(time / 1000) * 0.5 + 0.5);

  // show our bounding rectangle
  displayBoundingRectangle();
  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;
}
#info {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  color: white;
}



Если неясно, проблема в том, что одна из ваших точек находится за конусом усеченной камеры. Frustum определяет вид пирамиды отсечки от ближнего к дальнему, но математика, проходящая рядом (к камере), в конечном итоге переходит в одну точку, все, что находится за этой точкой, начинает расширяться обратно

enter image description here

...