如何在Three.js中以“背景大小:覆盖”方式使不平方的纹理适合几何平面?

时间:2019-06-17 14:18:10

标签: three.js textures

我希望我的纹理具有与“ background-size:cover” css属性相同的行为。

我想使用uvs坐标。 我看着这个答案,开始着手解决方案:Three.js Efficiently Mapping Uvs to Plane

我尝试使尺寸/位置平面与DOM的div相同。 这就是我想要的:

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

我应该使用纹理的repeat属性,还是可以使用uvs完全实现这种行为?谢谢

2 个答案:

答案 0 :(得分:2)

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

UV映射值的范围从01(包括端值),代表整个纹理图像的百分比映射。

您使用的是div大小与窗口大小的比率,该比率可能比1小得多,并且会导致您看到“放大”效果。

例如,如果wh的值为0.5,则映射纹理的最右上角将是图像的确切中心。 / p>

background-style: cover

  

在不拉伸图像的情况下尽可能放大图像。如果图像的比例与元素不同,则可以垂直或水平裁剪图像,以确保没有空白空间。

换句话说,它将根据短边的大小缩放图像,并裁剪其余部分。因此,假设您有一个不错的128x512图像和一个64x64空间。 cover会将128的宽度缩小到64(缩放因子0.5),因此将512乘以0.5得到新高度(128)。现在您的w仍为1,但您的h将为128 / 512 = 0.25。现在,您的纹理将适合宽度,并裁剪高度。

您需要针对每个图像与容器的尺寸关系执行此计算,以找到合适的UV,同时请记住缩放比例始终与短边相关。

答案 1 :(得分:0)

您不需要生成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实际上仅应用于UV,因此,如果您真的想要UV,则

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