Reposition UVs in Vertex Shader to maintain texture integrity without causing distortion

I'm working on rotating and scaling UVs in a vertex shader to ensure that the rotated texture completely fills its available bounding box. My current implementation is successfully rotating and auto-scaling the texture, but I'm facing an issue where the image gets skewed or distorted as the rotation value increases.

While I have accounted for the texture's aspect ratio for auto-scaling, there seems to be something missing in the rotation step that I'm unable to figure out.

Although this question seems related to my issue, I am struggling to adapt the proposed solution to my vertex shader due to a lack of understanding of how Three.js operates at a lower level.

I would greatly appreciate any assistance!

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/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84f0ecf6e1e1a9eef7c4b3bdaab4aab4">[email protected]</a>/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>

Answer β„–1

Identifying the Issue:

  • Upon focusing, it is essential to focus out;
  • The scaling may be inaccurate due to the MAD (multiplication then addition) process. To rectify this, multiply the scale from the origin point 0, 0, and then center. It is crucial to center before scaling.

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 p = uv;
    float mid = 0.5;    
    float aspect = tSize.x / tSize.y;
    
    float cosA = cos(rotation);
    float sinA = sin(rotation);
    float scale = 1.0 / max(abs(cosA) + abs(sinA) / aspect, abs(sinA) + abs(cosA) * aspect);
    
    mat2 rotMat = mat2(cosA, -sinA, sinA, cosA);

    p -= vec2(mid);
    p *= scale;
    p.y *= 1.0 / aspect;
    p *= rotMat;
    
    p.y *= aspect;
    p += vec2(mid);

    return p;

  }

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

// Setting up the scene
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);

// Loading an image and creating a mesh with matching 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;
  
  // Creating geometry based on the same aspect ratio
  const geometry = new THREE.PlaneGeometry(aspectRatio, 1);
  
  // Shader material
console.log(img.width, img.height);
  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;

  // Creating and adding mesh to the scene
  const mesh = new THREE.Mesh(geometry, shaderMaterial);
  scene.add(mesh);
  
  // User Interface 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/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aedac6dccbcb83c4ddee9997809e809e">[email protected]</a>/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>

Answer β„–2

After investigating further, I discovered that adjusting the texture scale is crucial to prevent distortion when manipulating textures. Through my experimentation with vertices, I was able to eliminate distortion during rotation, but it resulted in some scaling issues. As depicted, there is stretching of pixels at the edges when rotating. While this can be partially concealed, especially within patterns, it's a rather simple workaround. Typically, the goal is simply to eradicate distortions during rotation...

By the way, version r79 is considered quite outdated...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

ASPX responses can trigger bugs in Internet Explorer when attempting to write JSON

I'm having an issue with my ASPX page that is supposed to output JSON data. It's working perfectly fine in Firefox and Chrome, but when I try to run it in IE 8, I get an error saying "The XML page cannot be displayed" instead of loading the JSON ...

Exploring Node.js: Comparing Intl.NumberFormat and decimal.js library for floating point calculation

Can someone explain the distinction between Intl.NumberFormat and the decimal.js library? If Intl.NumberFormat can handle currency calculations, what is the purpose of using the decimal.js library and what advantages does it offer over Intl.NumberFormat ...

Discovering the method to tally responses in Discord using JavaScript

I created a custom poll command using discord.js, but I'm struggling to figure out how to retrieve the number of reactions on a specific emoji. Here's the code I have so far: if (!args[1]) return message.channel.send('Please ask a question.& ...

What is the best way to eliminate the "0"s from the center of items within an array?

I have developed a table sorter that handles columns containing both letters and numbers, such as "thing 027". To facilitate sorting, I prepended zeros to the numbers. However, I am now looking for a cleaner way to remove these zeros from the numbers using ...

Ajax/PHP - unrecognized value in autofill

I have implemented this particular example to execute an ajax data query from a MySQL database, which returns a table. Currently, this setup functions correctly when text is manually entered into the input form. Example: https://i.sstatic.net/TTgLz.jpg T ...

The toISOString() method is deducting a day from the specified value

One date format in question is as follows: Tue Oct 20 2020 00:00:00 GMT+0100 (Central European Standard Time) After using the method myValue.toISOString();, the resulting date is: 2020-10-19T23:00:00.000Z This output shows a subtraction of one day from ...

What is the best way to fill the dropdown options in every row of a data table?

This HTML snippet displays a data table with test data and corresponding dropdown options. <tr> <th> Test Data </th> <th> Test Data ...

What is the process for swapping true and false values ​​in HTML from JSON with online and offline statuses?

I am using a WordPress site and looking to utilize the API through JSON. The API provides a true/false result, displaying as either true or false. How can I showcase this information on the webpage as online when the result is true and offline when the res ...

XMLHttpRequest Error: The elusive 404 code appears despite the existence of the file

This is the organization of my project files: The folders Voice, Text, and Template are included. https://i.stack.imgur.com/9un9X.png When I execute python app.py and navigate to localhost http://0.0.0.0:8080/, the index.html page is displayed with conte ...

Executing an SQL delete query with a button click using a JavaScript function in PHP

I have created a setup with three essential files - index.html, database.php, and function.js. In database.php, there is a form generated containing a delete button that triggers the deletion SQL query when clicked. The primary objective is to present a ta ...

Extracting information from JSON structure

My JSON object response includes a `series` array of objects structured like this: { series: [ { name: 'a', data: [1,2,3] }, { name: 'b', data: [4,5,6] } ] } I am looking to extract the `data` values th ...

What is the most effective method for utilizing JSON as a database?

For my upcoming offline mobile web app project, I am considering using JSON to mirror some of my database tables and storing the data in localStorage. (Although Web SQL Database is an option, its future-proofing capabilities are questionable.) Initially, ...

Blank area located at the bottom of the document

I'm having trouble designing a webpage without a scroll bar because there isn't much content to display on the page. I've tried searching for solutions and following steps to fix the issue, but I haven't had any success. If anyone can a ...

AngularJS error: Uncaught MinError Object

Recently, I started a new AngularJS project and successfully set it up. The installation of angular and angular-resource using bower went smoothly. However, upon installing another service that I have used previously - https://github.com/Fundoo-Solutions/a ...

Is there a way to stop the continuous loading of AJAX requests?

For weeks, I have been facing a persistent issue that I've attempted to solve in various ways. The problem lies in my ajax search function. Every time I initiate a search, it continues loading multiple times. Surprisingly, the more searches I perform ...

Guide to using Vue.js and Vue Router to create a sub menu for the children of the current route

I am currently working on customizing the navigation for my Vue application. My goal is to create a main menu at the top that displays all the root level routes, and a side menu that appears when navigating through child routes. Here is an example of what ...

When is it appropriate to utilize a transpiler in JavaScript?

In the world of software development, a source-to-source compiler, also known as a transcompiler or transpiler, serves as a unique type of tool. Essentially, it takes the original source code of a program written in one programming language and generates e ...

How to extract a variable value from a different HTML page using jQuery

I have an HTML page named "page1.html" with the following content: <div> <div id="content"> content </div> <script type="text/javascript"> var x=5; var y=2; </script> <div id="ot ...

Toggle visibility of the last cell in a nested table by clicking on the first cell in each row of the table

I am looking to create a nested table structure for displaying data, as shown in the following link. Below is the code I have implemented in grid view in asp.net, but now I am attempting to implement it in HTML. The issue at hand: When clicking on the ...

I'm seeking guidance on how to delete a specific ul li element by its id after making an ajax request to delete a corresponding row in MySQL database. I'm unsure about the correct placement for the jQuery

I have created an app that displays a list of income items. Each item is contained within an li element with a unique id, along with the item description and a small trash icon. Currently, I have implemented code that triggers an AJAX call when the user cl ...