Three.js:如何为不规则几何体映射纹理(UV-Coordinates)
Absolutely! Custom UV coordinates are exactly what you need to fix that stretched side texture and achieve the projected look you showed in your Photoshop mockup. Let’s break down how to approach this step by step.
Why the default UVs are causing stretching
Three.js’s default BoxGeometry generates UVs by stretching the texture to fit each face’s original rectangular shape. When you modify the bottom vertices to create a tapered side, the default UVs still try to map the texture to the new trapezoidal face—resulting in that stretched effect you’re seeing. To fix this, we need to manually calculate UVs that "project" the texture onto the sides, ignoring the tapering.
How to create projected UVs for your custom geometry
The core idea is to map each vertex’s 3D position to a 2D UV coordinate based on the direction you want the texture to project (e.g., straight down the Y-axis, like a projector pointing at the sides). Here’s a practical example you can adapt to your modified geometry:
// Assume you have your modified BoxGeometry ready const customBox = yourModifiedBoxGeometry; // Extract position data and initialize a new UV array const positions = customBox.attributes.position.array; const uvData = []; // Get your original box dimensions (adjust if you modified these) const { width, height, depth } = customBox.parameters; // Loop through each vertex (3 values per vertex: x, y, z) for (let i = 0; i < positions.length; i += 3) { const x = positions[i]; const y = positions[i + 1]; const z = positions[i + 2]; // Calculate base UVs (for top/bottom faces) const baseU = (x + width / 2) / width; // Map X from [-width/2, width/2] to [0, 1] const baseV = (y + height / 2) / height; // Map Y from [-height/2, height/2] to [0, 1] // Check if this vertex belongs to a side face const isXSide = Math.abs(x - width/2) < 0.001 || Math.abs(x + width/2) < 0.001; const isZSide = Math.abs(z - depth/2) < 0.001 || Math.abs(z + depth/2) < 0.001; if (isXSide) { // For X-aligned sides, use Z and Y for UVs (projects texture along X-axis) const u = (z + depth / 2) / depth; uvData.push(u, baseV); } else if (isZSide) { // For Z-aligned sides, use X and Y for UVs (projects texture along Z-axis) const u = (x + width / 2) / width; uvData.push(u, baseV); } else { // Top/bottom faces: use base UVs or adjust as needed uvData.push(baseU, baseV); } } // Create a new UV attribute and assign it to the geometry customBox.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvData), 2)); // Important: Update geometry metadata after modifying attributes customBox.computeBoundingBox(); customBox.computeVertexNormals();
Key adjustments to fit your specific geometry
- Vertex detection: If your modified geometry’s side vertices don’t have exact
width/2ordepth/2values (e.g., you moved them inward), adjust theisXSide/isZSidechecks to match your actual vertex positions. - Texture scaling: If you want the texture to repeat or fit differently, multiply the UV values by a scaling factor (e.g.,
u = ((z + depth/2)/depth) * 2to repeat the texture twice horizontally). - Texture orientation: If your texture appears upside down, reverse the
vvalue with1 - baseV.
Final notes
This approach gives you full control over how the texture is mapped to your custom geometry, exactly replicating the projected look you created in Photoshop. The key is to think of UV mapping as a way to "project" your 2D texture onto the 3D surface, rather than stretching it to fit each face’s shape.
内容的提问来源于stack exchange,提问作者Fabian

