我需要在顶点着色器中旋转和zoom UV,以便旋转的纹理填充其可用边界框.以下测试实现成功地旋转和自动zoom 纹理,但随着旋转值的增加,图像会倾斜/扭曲.
我正在考虑纹理的长宽比来进行自动zoom ,但我肯定在旋转步骤中遗漏了一些东西.
This question似乎相关,但我无法将建议的解决方案转换到我的顶点着色器,因为我不知道Three.js如何在引擎盖下工作.
如有任何帮助,不胜感激!
const VERTEX_SHADER = (`
varying vec2 vUv;
uniform vec2 tSize; // Texture size (width, height)
uniform float rotation; // Rotation angle in radians
vec2 rotateAndScaleUV(vec2 uv, float angle, vec2 tSize) {
vec2 center = vec2(0.5);
// Step 1: Move UVs to origin for rotation
vec2 uvOrigin = uv - center;
// Step 2: Apply rotation matrix
float cosA = cos(rotation);
float sinA = sin(rotation);
mat2 rotMat = mat2(cosA, -sinA, sinA, cosA);
vec2 rotatedUv = rotMat * uvOrigin;
// Step 3: Auto-scale to fill available space
float aspectRatio = tSize.x / tSize.y;
float scale = 1.0 / max(abs(cosA) + abs(sinA) / aspectRatio, abs(sinA) + abs(cosA) * aspectRatio);
return rotatedUv * scale + center; // Scale and move back to correct position
}
void main() {
vUv = rotateAndScaleUV(uv, rotation, tSize);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`);
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
// Load an image and create a mesh that matches its aspect ratio
new THREE.TextureLoader().load('https://images.unsplash.com/photo-1551893478-d726eaf0442c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MDcyNDI0MTB8&ixlib=rb-4.0.3&q=80&w=400', texture => {
texture.minFilter = THREE.LinearFilter;
texture.generateMipMaps = false;
const img = texture.image;
const aspectRatio = img.width / img.height;
// Create geometry with the same aspect ratio
const geometry = new THREE.PlaneGeometry(aspectRatio, 1);
// Shader material
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
textureMap: { value: texture },
tSize: { value: [img.width, img.height] },
rotation: { value: 0 }
},
vertexShader: VERTEX_SHADER,
fragmentShader: `
uniform sampler2D textureMap;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(textureMap, vUv);
}
`
});
camera.position.z = 1;
// Create and add mesh to the scene
const mesh = new THREE.Mesh(geometry, shaderMaterial);
scene.add(mesh);
// UI controls
document.getElementById('rotation').addEventListener('input', e => {
shaderMaterial.uniforms.rotation.value = parseFloat(e.target.value);
renderer.render(scene, camera);
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
}, false);
renderer.render(scene, camera);
});
body {margin: 0; color: grey;}
#container {
width: 100vw;
height: 100vh;
}
#ui {
position: absolute;
top: 5%;
left: 50%;
transform: translateX(-50%);
z-index: 10;
}
<script src="https://cdn.jsdelivr.net/npm/three-js@79.0.0/three.min.js"></script>
<div id="container"></div>
<div id="ui">
<label for="rotation">Rotation:</label>
<input type="range" id="rotation" min="-1" max="1" step="0.001" value="0">
</div>