简体   繁体   中英

THREEjs - crop texture instead of stretching-to-fit

I'm attempting to build an image-based art-making app using three.js.

I'm using React.js, but hopefully the following is clear even for those without React experience. The component renders a , and it's passed a locally-hosted image URL from a parent component.

The current code is set up to simply create a full-window canvas, create a 2D shape that spans the entire canvas, and set the provided image as a texture:

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;

The issue I'm having is that the texture will be stretched to fit. This means that if the image is a square 1600x1600, but the browser window is 1000x500, the image will be squashed and distorted, since it's mapping the image to fit perfectly in the available space:

被压扁的女人

What I want is an equivalent of background-size: cover . I want it to shrink to the largest window dimension (1000x1000, in my example), and then crop from the center to only show the middle 500px worth of height.

You need to show your camera, are you using a PerspectiveCamera , an OrthographicCamera or a place holder Camera . And, if you're using a custom shader you need to show how you calculate your vertex positions (do you they use projectionMatrix or not etc...). Without that knowledge no one can answer the question.

Here's how to do it using an OrthographicCamera just set to become the identity matrix and a 2 unit PlaneGeometry .

If we changed nothing else this would display a plane that fills the canvas. After that we scale it taking into account the aspect of the canvas and the aspect of the image

  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;

 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(); }); 
 body { margin: 0; } canvas { width: 100vw; height: 100vh; display: block; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM