如何使非方形纹理以 "background-size:cover" 的方式适合 Three.js 中的几何平面?
How to make a not squared texture fit in a "background-size:cover" way to a geometry plane in Three.js?
我希望我的纹理与 "background-size:cover" css 属性 具有相同的行为。
我想使用 uvs 坐标。
我查看了这个答案并开始研究解决方案:Three.js Efficiently Mapping Uvs to Plane
我尝试拥有与我的 DOM 中的某些 div 相同的 dimension/position 飞机。
这就是我想要的:
这是我用这段代码得到的结果:尺寸和位置都很好,我的纹理比例看起来也不错,但似乎存在比例问题:
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 );
我应该使用纹理的重复属性还是可以使用 uvs 完全实现此行为?谢谢
https://en.wikipedia.org/wiki/UV_mapping
UV 映射值的范围从 0
到 1
(含),代表整个纹理图像的百分比映射。
您使用的是 div 尺寸与 window 尺寸的比率,这可能比 1
小得多,并且会导致 "zoomed in"您看到的效果。
例如,如果您的 w
和 h
的结果是值 0.5
,那么映射纹理的最远 top-right 角将是图片。
Scales the image as large as possible without stretching the image. If the proportions of the image differ from the element, it is cropped either vertically or horizontally so that no empty space remains.
换句话说,它会根据短边的大小缩放图像,并裁剪其余部分。因此,假设您有一张不错的 128x512
图片和一张 64x64
space。 cover
会将 128
的宽度缩小到 64
(0.5
的比例因子),因此 512
乘以 0.5
得到新高度 (128
)。现在你的 w
仍然是 1
,但你的 h
将是 128 / 512 = 0.25
。您的纹理现在将适合宽度,并裁剪高度。
您需要为每个 image-to-container 大小关系执行此计算以找到合适的 UV,请记住缩放始终与短边相关。
您不需要生成 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 );
我希望我的纹理与 "background-size:cover" css 属性 具有相同的行为。
我想使用 uvs 坐标。 我查看了这个答案并开始研究解决方案:Three.js Efficiently Mapping Uvs to Plane
我尝试拥有与我的 DOM 中的某些 div 相同的 dimension/position 飞机。 这就是我想要的:
这是我用这段代码得到的结果:尺寸和位置都很好,我的纹理比例看起来也不错,但似乎存在比例问题:
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 );
我应该使用纹理的重复属性还是可以使用 uvs 完全实现此行为?谢谢
https://en.wikipedia.org/wiki/UV_mapping
UV 映射值的范围从 0
到 1
(含),代表整个纹理图像的百分比映射。
您使用的是 div 尺寸与 window 尺寸的比率,这可能比 1
小得多,并且会导致 "zoomed in"您看到的效果。
例如,如果您的 w
和 h
的结果是值 0.5
,那么映射纹理的最远 top-right 角将是图片。
Scales the image as large as possible without stretching the image. If the proportions of the image differ from the element, it is cropped either vertically or horizontally so that no empty space remains.
换句话说,它会根据短边的大小缩放图像,并裁剪其余部分。因此,假设您有一张不错的 128x512
图片和一张 64x64
space。 cover
会将 128
的宽度缩小到 64
(0.5
的比例因子),因此 512
乘以 0.5
得到新高度 (128
)。现在你的 w
仍然是 1
,但你的 h
将是 128 / 512 = 0.25
。您的纹理现在将适合宽度,并裁剪高度。
您需要为每个 image-to-container 大小关系执行此计算以找到合适的 UV,请记住缩放始终与短边相关。
您不需要生成 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 );