我尝试使用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,图像将被压扁和扭曲,因为它将图像映射到完全适合可用空间:
我想要的是等同于background-size: cover
。我希望它缩小到最大的窗口尺寸(在我的例子中为1000x1000),然后从中心裁剪到仅显示中间500px的高度。
答案 0 :(得分:1)
您需要展示自己的相机,使用的是PerspectiveCamera
,OrthographicCamera
还是占位符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;
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;