THREEjs - 裁剪纹理而不是拉伸到合身

时间:2017-06-23 11:36:38

标签: javascript three.js

我尝试使用three.js构建基于图像的艺术制作应用。

我使用的是React.js,但希望即使没有React经验的人也能清楚以下内容。该组件呈现a,并且它从父组件传递了本地托管的图像URL。

当前代码设置为简单地创建一个全窗口画布,创建一个跨越整个画布的2D形状,并将提供的图像设置为纹理:

import React, { PureComponent } from 'react';
import * as THREE from 'three';

const textureLoader = new THREE.TextureLoader();

const vertexShader = `
  varying vec2 vUv;

  void main() {
      vUv = uv;
      gl_Position = vec4(uv * 2. - vec2(1, 1), 0., 1.);
  }
`;

const fragmentShader = `
  precision highp float;

  varying vec2 vUv;
  uniform sampler2D texture1;
  void main() {
      gl_FragColor = texture2D(texture1, vUv.xy);
  }
`;

class Canvas extends PureComponent {
  state = {
    width: window.innerWidth,
    height: window.innerHeight
  }

  componentDidMount() {
    // TODO: resize handlers for width/height
    const {width, height} = this.state;

    this.canvas.width = width;
    this.canvas.height = height;

    const rendererParams = {
      canvas: this.canvas,
    };
    const cameraParams = [
      -width / 2,
      width / 2,
      -height / 2,
      height / 2,
    ];

    this.renderer = new THREE.WebGLRenderer(rendererParams);
    this.renderer.setClearColor(0xFFFFFF, 1.0)
    this.renderer.setSize(width, height);

    this.camera = new THREE.OrthographicCamera(...cameraParams);
    this.camera.position.set(0, 0, 200);

    this.scene = new THREE.Scene();
    this.scene.add(this.camera);

    // Our initial texture will be the specified default image.
    const imageSrc = this.props.defaultImageSrc;

    textureLoader.load(imageSrc, texture => {
      this.texture = texture;

      // TODO: Do I need this?
      this.material = new THREE.ShaderMaterial({
        vertexShader,
        fragmentShader,
        uniforms: {
          texture1: {
            type: "t",
            value: this.texture
          }
        }
      });

      this.mesh = new THREE.Mesh(new THREE.PlaneGeometry(1), this.material);

      this.scene.add(this.mesh);

      this.animate();
    })
  }

  animate = () => {
    requestAnimationFrame(this.animate);

    this.renderer.render(this.scene, this.camera);
  }

  render() {
    return (
      <canvas innerRef={elem => this.canvas = elem} />
    );
  }
}

export default Canvas;

我遇到的问题是纹理会被拉伸以适应。这意味着如果图像是方形1600x1600,但浏览器窗口是1000x500,图像将被压扁和扭曲,因为它将图像映射到完全适合可用空间:

squashed woman

我想要的是等同于background-size: cover。我希望它缩小到最大的窗口尺寸(在我的例子中为1000x1000),然后从中心裁剪到仅显示中间500px的高度。

1 个答案:

答案 0 :(得分:1)

您需要展示自己的相机,使用的是PerspectiveCameraOrthographicCamera还是占位符Camera。而且,如果您正在使用自定义着色器,则需要显示如何计算顶点位置(他们是否使用projectionMatrix等等...)。没有这些知识,没人能回答这个问题。

以下是如何使用OrthographicCamera设置成为单位矩阵和2单位PlaneGeometry的方法。

如果我们没有改变任何其他内容,则会显示填充画布的平面。之后,我们考虑画布的方面和图像的方面来缩放它

  const canvas = renderer.domElement;
  const image = material.map.image;
  const canvasAspect = canvas.clientWidth / canvas.clientHeight;
  const imageAspect = image.width / image.height;

  // this assumes we want to fill vertically
  let horizontalDrawAspect = imageAspect / canvasAspect;
  let verticalDrawAspect = 1;
  // does it fill horizontally?
  if (horizontalDrawAspect < 1) {
    // no it does not so scale so we fill horizontally and
    // adjust vertical to match
    verticalDrawAspect /= horizontalDrawAspect;
    horizontalDrawAspect = 1;
  }
  mesh.scale.x = horizontalDrawAspect;
  mesh.scale.y = verticalDrawAspect;

&#13;
&#13;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);

const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.MeshBasicMaterial({});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

camera.position.z = 1;

function render() {
  resize();
  
  if (!material.map.image) {
    return;
  }
  
  const canvas = renderer.domElement;
  const image = material.map.image;
  const canvasAspect = canvas.clientWidth / canvas.clientHeight;
  const imageAspect = image.width / image.height;
  
  // this assumes we want to fill vertically
  let horizontalDrawAspect = imageAspect / canvasAspect;
  let verticalDrawAspect = 1;
  // does it fill horizontally?
  if (horizontalDrawAspect < 1) {
    // no it does not so scale so we fill horizontally and
    // adjust vertical to match
    verticalDrawAspect /= horizontalDrawAspect;
    horizontalDrawAspect = 1;
  }
  mesh.scale.x = horizontalDrawAspect;
  mesh.scale.y = verticalDrawAspect;

  
  renderer.render(scene, camera);
}

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

window.addEventListener('resize', render);

const loader = new THREE.TextureLoader();
loader.crossOrigin = "";

// load a resource
loader.load('http://i.imgur.com/TSiyiJv.jpg', function (texture) {
  texture.wrapS = THREE.ClampToEdgeWrapping;
  texture.wrapT = THREE.ClampToEdgeWrapping;
  texture.minFilter = THREE.LinearFilter;
  material.map = texture;
  render();
});
&#13;
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>
&#13;
&#13;
&#13;