Как сделать так, чтобы не квадратная текстура подходила способом "background-size: cover" к геометрической плоскости в Three.js? - PullRequest
1 голос
/ 17 июня 2019

Я хочу, чтобы моя текстура имела такое же поведение, что и свойство css "background-size: cover".

Я бы хотел работать с координатами uvs.Я посмотрел на этот ответ и начал работать над решением: Three.js Эффективное отображение Uvs на плоскость

Я пытаюсь иметь те же плоскости измерения / положения, что и некоторый div моего DOM.Это то, что я хочу:

enter image description here

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

            let w = domElmt.clientWidth / window.innerHeight;
            let h = domElmt.clientHeight / window.innerHeight;
            geometry = new THREE.PlaneGeometry(w, h);

            var uvs = geometry.faceVertexUvs[ 0 ];
            uvs[ 0 ][ 0 ].set( 0, h );
            uvs[ 0 ][ 1 ].set( 0, 0 );
            uvs[ 0 ][ 2 ].set( w, h );
            uvs[ 1 ][ 0 ].set( 0, 0 );
            uvs[ 1 ][ 1 ].set( w, 0 );
            uvs[ 1 ][ 2 ].set( w, h );

            tex = new THREE.TextureLoader().load('image.jpg'));
            tex.wrapS = tex.wrapT = THREE.RepeatWrapping;

            material = new THREE.MeshBasicMaterial( { map: tex } );
            mesh = new THREE.Mesh( geometry, material );

enter image description here

Должен ли я играть с атрибутом повторения моей текстуры или я могу полностьюсделал это поведение с помощью uvs?Спасибо

Ответы [ 2 ]

2 голосов
/ 17 июня 2019

https://en.wikipedia.org/wiki/UV_mapping

Значения UV-сопоставления варьируются от 0 до 1 включительно и представляют процентное сопоставление для вашего изображения текстуры.

Вы используете отношение размера div к размеру окна, которое, вероятно, намного меньше, чем 1, и приведет к эффекту "увеличения", который вы видите.

Например, если ваши w и h приводят к значению 0.5, тогда самый дальний верхний правый угол отображаемой текстуры будет точным центром изображения.

background-style: cover

Масштабирует изображение как можно больше, не растягивая его. Если пропорции изображения отличаются от элемента, оно обрезается либо по вертикали, либо по горизонтали, чтобы не осталось пустого места.

Другими словами, оно будет масштабировать изображение в зависимости от размера короткой стороны и обрезать остальное. Итак, давайте предположим, что у вас есть хорошее 128x512 изображение и 64x64 пространство. cover уменьшит ширину 128 до 64 (коэффициент масштабирования 0.5), поэтому умножьте 512 на 0.5, чтобы получить новую высоту (128). Теперь ваш w все равно будет 1, но ваш h будет 128 / 512 = 0.25. Ваша текстура теперь будет соответствовать ширине и обрезать высоту.

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

0 голосов
/ 18 июня 2019

Вам не нужно генерировать UV, вы можете просто использовать texture.repeat и texture.offset

    const aspectOfPlane = planeWidth / planeHeight;
    const aspectOfImage = image.width / image.height;
    let yScale = 1;
    let xScale = aspectOfPlane / aspectOfImage;
    if (xScale > 1) { // it doesn't cover so based on x instead
      xScale = 1;
      yScale = aspectOfImage / aspectOfPlane;
    }
    texture.repeat.set(xScale, yScale);
    texture.offset.set((1 - xScale) / 2, (1 - yScale) / 2); 

'use strict';

/* global THREE */

async function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 50;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 4;

  const scene = new THREE.Scene();

  const loader = new THREE.TextureLoader();
  function loadTexture(url) {
    return new Promise((resolve, reject) => {
      loader.load(url, resolve, undefined, reject);
    });
  }
  
  const textures = await Promise.all([
    "https://i.imgur.com/AyOufBk.jpg",
    "https://i.imgur.com/ZKMnXce.png",
    "https://i.imgur.com/TSiyiJv.jpg",
    "https://i.imgur.com/v38pV.jpg",
  ].map(loadTexture));

  const geometry = new THREE.PlaneBufferGeometry(1, 1);
  const material = new THREE.MeshBasicMaterial({map: textures[0]});
  const planeMesh = new THREE.Mesh(geometry, material);
  scene.add(planeMesh);
  
  let texIndex = 0;
  function setTexture() {
    const texture = textures[texIndex];
    texIndex = (texIndex + 1) % textures.length;

    // pick and random width and height for plane
    const planeWidth = rand(1, 4);
    const planeHeight = rand(1, 4);
    planeMesh.scale.set(planeWidth, planeHeight, 1);
 
    const image = texture.image;
    
    const aspectOfPlane = planeWidth / planeHeight;
    const aspectOfImage = image.width / image.height;
    let yScale = 1;
    let xScale = aspectOfPlane / aspectOfImage;
    if (xScale > 1) { // it doesn't cover so based on x instead
      xScale = 1;
      yScale = aspectOfImage / aspectOfPlane;
    }
    texture.repeat.set(xScale, yScale);
    texture.offset.set((1 - xScale) / 2, (1 - yScale) / 2);    
    material.map = texture;
  }
  
  setTexture();
  setInterval(setTexture, 1000);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

main();
body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>

texture.repeat и texture.offset действительно применяются только к ультрафиолетовым лучам, поэтому, если вам действительно нужны ультрафиолетовые лучи, это

newU = u * repeat.x + offset.x;
newV = v * repeat.y + offset.y;

поэтому используйте код выше

offsetX = (1 - xScale) / 2;
offsetY = (1 - yScale) / 2;
u0 = offsetX;
v0 = offsetY;
u1 = offsetX + xScale;
v1 = offsetY + yScale;

так

        var uvs = geometry.faceVertexUvs[ 0 ];
        uvs[ 0 ][ 0 ].set( u0, v1 );
        uvs[ 0 ][ 1 ].set( u0, v0 );
        uvs[ 0 ][ 2 ].set( u1, v1 );
        uvs[ 1 ][ 0 ].set( u0, v0 );
        uvs[ 1 ][ 1 ].set( u1, v0 );
        uvs[ 1 ][ 2 ].set( u1, v1 );
...