You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Vertex Shader中旋转UV并缩放填充边界且无纹理扭曲?

解决顶点着色器中UV旋转缩放后的纹理扭曲问题

问题分析

当前实现直接在原始UV空间(因平面宽高比不为1,UV的x/y单位长度不等)执行旋转操作,导致旋转后的缩放计算出现偏差,引发纹理扭曲。需先将UV转换到正方形归一化空间完成旋转,再还原并计算正确的填充缩放。

修正方案

核心修改聚焦于rotateAndScaleUV函数:

  1. 先根据平面宽高比将UV拉伸为正方形空间,消除宽高比对旋转的影响
  2. 在正方形空间内完成旋转操作
  3. 将旋转后的UV缩回到原始宽高比空间
  4. 重新计算填充缩放因子,确保纹理完全填充平面边界框

修正后的完整代码

JavaScript代码

const VERTEX_SHADER = (`
  varying vec2 vUv;
  uniform vec2 planeAspect; // 平面的宽高比(width/height)
  uniform float rotation; // 旋转角度(弧度)

  vec2 rotateAndScaleUV(vec2 uv, float angle, vec2 planeAspect) {
    vec2 center = vec2(0.5);

    // 步骤1:将UV转换到正方形归一化空间(消除宽高比影响)
    vec2 uvSquare = uv - center;
    uvSquare.x *= planeAspect.x; // 拉伸x轴到与y轴等长
    uvSquare += center;

    // 步骤2:移动到原点并执行旋转
    vec2 uvOrigin = uvSquare - center;
    float cosA = cos(angle);
    float sinA = sin(angle);
    mat2 rotMat = mat2(cosA, -sinA, sinA, cosA);
    vec2 rotatedUvSquare = rotMat * uvOrigin;

    // 步骤3:将旋转后的UV还原到原始宽高比空间
    vec2 rotatedUv = rotatedUvSquare + center;
    rotatedUv.x /= planeAspect.x;

    // 步骤4:计算填充缩放因子
    float scale = 1.0 / max(abs(cosA) + abs(sinA) * planeAspect.x, abs(sinA) / planeAspect.x + abs(cosA));
    rotatedUv = (rotatedUv - center) * scale + center;

    return rotatedUv;
  }

  void main() {
    vUv = rotateAndScaleUV(uv, rotation, planeAspect);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`);

// 场景初始化
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);

// 加载纹理并创建平面
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 planeAspect = [img.width / img.height, 1]; // 平面的宽高比(x/y)
  
  // 创建与图片同宽高比的平面几何体
  const geometry = new THREE.PlaneGeometry(planeAspect[0], planeAspect[1]);
  
  // 着色器材质
  const shaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
      textureMap: { value: texture },
      planeAspect: { value: planeAspect },
      rotation: { value: 0 }
    },
    vertexShader: VERTEX_SHADER,
    fragmentShader: `
      uniform sampler2D textureMap;
      varying vec2 vUv;
      void main() {
        gl_FragColor = texture2D(textureMap, vUv);
      }
    `
  });
  
  camera.position.z = 1;

  // 创建网格并添加到场景
  const mesh = new THREE.Mesh(geometry, shaderMaterial);
  scene.add(mesh);
  
  // 旋转控制
  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);
  
});

CSS代码

body {margin: 0; color: grey;}
#container {
  width: 100vw;
  height: 100vh;
}
#ui {
  position: absolute;
  top: 5%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
}

HTML代码

<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">旋转角度:</label>
  <input type="range" id="rotation" min="-1" max="1" step="0.001" value="0">
</div>

关键修改说明

  • 替换uniform变量:用planeAspect(平面的宽高比)替代原tSize,直接传递平面宽高比更高效,无需重复计算纹理尺寸比例
  • UV正方形空间转换:旋转前将UV的x轴按平面宽高比拉伸,让UV空间变为正方形,确保旋转操作在等比例空间中执行,从根源避免因UV空间非正方形导致的纹理扭曲
  • 修正缩放因子计算:基于平面宽高比重新推导填充缩放公式,保证旋转后的纹理始终完全填满平面的边界框

内容的提问来源于stack exchange,提问作者RoyNeary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:17:32