在顶点着色器中旋转 UV,而不扭曲纹理

问题描述 投票:0回答:1

我需要在顶点着色器中旋转和缩放 UV,以便旋转的纹理填充其可用的边界框。以下测试实现成功地旋转并自动缩放纹理,但随着旋转值的增加,图像会倾斜/扭曲。

我正在考虑纹理的纵横比以进行自动缩放,但我肯定在旋转步骤中遗漏了一些东西。

这个问题似乎相关,但我无法将建议的解决方案转换为我的顶点着色器,因为我不知道 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/[email protected]/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>

javascript matrix three.js vertex-shader uv-mapping
1个回答
0
投票

我对此进行了一些研究,似乎为了在操作纹理时避免失真,您需要调整纹理比例。我对顶点进行了一些实验,并设法消除了旋转时的扭曲,但这是以缩放为代价的。正如您所看到的,旋转时,最后的像素被拉伸。这可能会被“屏蔽”一点,特别是在模式中,但这是一个非常便宜的解决方案。一般来说,您只想消除旋转时的扭曲... 顺便说一句,r79 是相当旧的版本...

const VERTEX_SHADER = (` varying vec2 vUv; uniform vec2 tSize; uniform float rotation; uniform float scaleValue; vec2 rotateAndScaleUV(vec2 uv, float angle, vec2 tSize) { vec2 center = vec2(0.5); vec2 uvOrigin = uv - center; float cosA = cos(angle); float sinA = sin(angle); mat2 rotMat = mat2(cosA, -sinA, sinA, cosA); vec2 rotatedUv = rotMat * uvOrigin; vec2 scaling = vec2(1.0); float aspectRatio = tSize.x / tSize.y; if (aspectRatio > 1.0) { scaling.x = 1.0 / aspectRatio; } else { scaling.y = aspectRatio; } rotatedUv *= scaling * scaleValue; rotatedUv += center; return rotatedUv; } void main() { vec2 centeredUV = position.xy + vec2(0.5); vUv = rotateAndScaleUV(centeredUV, 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; // Like you see, when you commented wrapping, last pixels are streched. You can make this improvement, but is quite... cheap, and decent for visually impaired people... and seriously, it may only work a little in the patterns. texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; 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 }, scaleValue: { value: 1 }, }, vertexShader: VERTEX_SHADER, fragmentShader: ` uniform sampler2D textureMap; varying vec2 vUv; uniform float scaleValue; // Add scaleValue uniform void main() { vec2 scaledUV = vUv * scaleValue; // Scale UV coordinates gl_FragColor = texture2D(textureMap, scaledUV); } ` }); 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); }); document.getElementById('scale').addEventListener('input', e => { shaderMaterial.uniforms.scaleValue.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: 2%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  justify-content: center;
}
#ui label {
  margin-right:
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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">
  <label for="scale">Scale:</label>
  <input type="range" id="scale" min="0.1" max="2" step="0.01" value="1">
</div>

© www.soinside.com 2019 - 2024. All rights reserved.