Using numerous textures for a single mesh in THREE.js

I am working with an OBJ file that utilizes four textures. The UV coordinates in the file range from (0, 0) to (2, 2), where (0.5, 0.5) corresponds to a coordinate in the first texture, (0.5, 1.5) is a UV coordinate in the second texture, (1.5, 0.5) represents a coordinate in the third texture, and (1.5, 1.5) points to a coordinate in the last texture.

Currently, I have the correct three.js geometry or object set up. However, I need assistance in applying the appropriate texture maps to these objects.

In my code:

I have a THREE.Mesh with the proper geometry (with UVs coords ranging from U = [0, 2], V = [0, 2]) and a temporary placeholder material. I load a single texture using the following method:

var texture = new THREE.TextureLoader().load('tex_u1_v1.png', function() {
    object.material.map = texture;
    object.material.map.needsUpdate = true;
});

As expected, only one section of the mesh is textured correctly. I have three additional texture files: tex_u1_v2.png, tex_u2_v1.png, and tex_u2_v2.png. My goal is to apply these textures as well to the object (the THREE.js mesh), ensuring that there is a corresponding texture for every valid UV point on the mesh.

However, I am unsure how to add multiple materials to the object after its creation. Furthermore, I am unsure how to specify to the mesh that, for example, tex_u1_v2.png should be used for UV coordinates within the specified range (U = [0, 2], V = [1, 2]).

Answer №1

In Three, the standard materials can only accept a single texture object for the different map parameters. In order to apply multiple textures to your object, you will need to utilize either multiple materials or create a custom multi-texture material.

If you are familiar with shader programming, creating your own shader using ShaderMaterial or RawShaderMaterial may yield the best performance. This approach allows you to draw the entire mesh in a single call without loading new shaders or textures.

To assign more than one material to an object, you can set the material property to an array of materials when creating the object or later on manually.

const myMaterials = [tex1Material, tex2Material, tex3Material, tex4Material];
const myMesh = new THREE.Mesh(myGeometry, myMaterials);
//Or:
myMesh.materials = myMaterials;

To specify which parts of the mesh use which materials, you'll need to create groups for BufferGeometry or set the materialIndex for Geometry. The material index corresponds to the index of the material in the mesh.material array.

Each material can then have its own textures applied to it based on the UV coordinates of the mesh parts.

  • To simplify texture mapping, ensure each part of the UV coordinate falls within the [0,1] interval to prevent overlap since unique materials are used.

If you prefer not to adjust existing coordinates, there are two alternatives:

  • Set the texture wrapping to THREE.RepeatWrapping:

    myTexture.wrapS = THREE.RepeatWrapping;
    myTexture.wrapT = THREE.RepeatWrapping;
    

    This enables the texture to repeat beyond the standard [0-1] uv interval.

  • Use the offset property of the texture to adjust its position back into the [0-1] interval. For example, setting the offset of the v-coordinate by -1 would place the texture in the u[0,1], v[1,2] interval:

    myTexture.offset = new THREE.Vector2(0, -1);
    

For a demonstration of these methods, check out this jsfiddle: https://jsfiddle.net/xfehvmb4/

Answer №2

I currently have a THREE.Mesh with precise geometry (featuring UVs coordinates where U = [0, 2] and V = [0, 2])

Your interpretation of "correct" may not be accurate in this context. Let's take a tetrahedron as an example and map it out to create 4 triangles. Each triangle will be placed in its own quadrant within UV space. These four quadrants are:

02--12--22
 | B | C |
01--11--21
 | A | D |
00--10--20

Explaining this concept can be challenging, but essentially the texture lookup always occurs in quadrant A (0011), for better understanding. Any value that exceeds 0 or 1 wraps around and references the same area. As a result, all 4 triangles (ABCD) are actually overlapping. There is no texture beyond this range - it either clamps to the edge pixel or wraps around (or possibly mirrors).

While there could be reasons for having UVs outside this range, it doesn't seem relevant in your scenario. If you don't have triangles crossing these boundaries, consider creating 4 separate meshes each with UVs in the 0,1 domain using individual textures.

Another approach suggested in a different response involves utilizing an array of materials and setting groups, which should suffice. Rendering a mesh with UVs ranging from 1,1 to 2,2 would yield the same result as if they were between 0,0 to 1,1.

Answer №3

My opinion on @Jave's answer is positive, but if you prefer the ShaderMaterial approach, here's a guide on how to achieve it:

// Creating the material
var material = new THREE.ShaderMaterial({
      uniforms: {
        u_tex1: {value: null},
        u_tex2: {value: null},
        u_tex3: {value: null},
        u_tex4: {value: null},
      },
      vertexShader: `
        varying vec2 v_uv;
        varying float v_textureIndex;
        void main() {
          // The UV mapping for textures [0, 1, 2, 3]
          v_textureIndex = step(0.5, uv.x) + step(0.5, uv.y) * 2.0;
          v_uv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying vec2 v_uv;
        varying float v_textureIndex;
        uniform sampler2D u_tex1;
        uniform sampler2D u_tex2;
        uniform sampler2D u_tex3;
        uniform sampler2D u_tex4;
        void main() {
          vec4 color = texture2D(u_tex1, v_uv);
          color = mix(color, texture2D(u_tex2, v_uv), step(0.5, v_textureIndex));
          color = mix(color, texture2D(u_tex3, v_uv), step(1.5, v_textureIndex));
          color = mix(color, texture2D(u_tex4, v_uv), step(2.5, v_textureIndex));
          gl_FragColor = color;
        }
      `,
    });

// Loading Textures
var texture1 = new THREE.TextureLoader().load('tex_u1_v1.png', function() { material.uniforms.u_tex1.value = texture1 });
var texture2 = new THREE.TextureLoader().load('tex_u2_v1.png', function() { material.uniforms.u_tex2.value = texture2 });
var texture3 = new THREE.TextureLoader().load('tex_u1_v2.png', function() { material.uniforms.u_tex3.value = texture3 });
var texture4 = new THREE.TextureLoader().load('tex_u2_v2.png', function() { material.uniforms.u_tex4.value = texture4 });

This method may slightly increase processing time due to 4 texture samples, but offers great flexibility.

Answer №4

It is possible to blend different textures together in a single mesh, such as:

  • bump map
  • diffuse
  • normal map ...and more

I recently acquired @types/three, which greatly assisted me in understanding the properties of MeshPhongMaterial. This particular material was used to instantiate my mesh material, with features like bumpMap, map, normalMap, and others at my disposal. Additionally, there is a color property that allows for changing the color of the texture.

Answer №5

After spending a week troubleshooting the code, I finally came up with this solution:

  1. If the 3D object is loaded, adjust its UV mapping in any 3D software (such as blender) to Modal. This will enhance the mesh of the object in Three.js code when exported. Then, load it in Three.js.

  2. You can retrieve Mesh details by logging the 3D object in the console:

    loader.load(
      './../../assets/img/3d/1/tshirtv4.glb',
      gltf => {
        let tshirtObj = gltf.scene;
        tshirtObj.position.set(0, 0, 0);
        // text canvas as Texture function
        console.log('Modal', tshirtObj);
        let MeshCommon = tshirtObj.getObjectByName('t-shirt002_1');
        let MeshBack = tshirtObj.getObjectByName('t-shirt002_2');
        let MeshSleeve = tshirtObj.getObjectByName('t-shirt002_3');
        
        const texturePatchUV2 = tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv2.array;
        tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv.array = texturePatchUV2;
        let texturePatchUV1 = tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv.array
        **console.log('Patchuv', texturePatchUV1)**
        function sceneUpdate() {
          //console.log('Mesh Patch material', MeshSleeve.material);
          const material = materialV2();
          gltf.scene.traverse(child => {
            if (child.isMesh) {
              //child.material = material[0];
              MeshCommon.material = material[0];
              MeshBack.material = material[1];
              MeshSleeve.material = material[2];

              // child.material = materialV2();
            }
          });
          scene.add(tshirtObj);
        }
        sceneUpdate();

        jsNameInput.addEventListener('input', sceneUpdate);
        jsNumberInput.addEventListener('input', sceneUpdate);
      },
      // called while loading is progressing
      function (xhr) {
        // console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
      },
      // called when loading has errors
      function (error) {
        console.error(error);
      }
    );
  1. Next, replace the UV map with the desired uvmap by assigning uv2 to uv as shown in the screenshot:
const texturePatchUV2= tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv2.array;
tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv.array = texturePatchUV2;
let texturePatchUV1 = tshirtObj.getObjectByName('t-shirt002_3').geometry.attributes.uv.array
console.log('Patchuv', texturePatchUV1)

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

Prevent the event from spreading down to the child elements

I believed I had a solid understanding of the concept of event bubbling, but now I am starting to doubt my grasp on it. I designed a semi-modal dialog like so: <div class="context-menu"> <div class="menu"> … </div> < ...

The functionality of Javascript Regular Expressions is not performing as expected

I am currently facing an issue with email validation in JavaScript as it doesn't seem to be working properly. PROBLEM: Even when entering a VALID email address, the alert still shows that my address is faulty... What could I possibly be missing he ...

Troubles encountered when cascading val(), text(), and data()

Here is my JavaScript/jQuery code: $select.append($("<option />") .val(this.id) .text(this.text) .data('name', this.name) .data('isstorage', this.isstorage)); Although it successfully assigns values to t ...

Achieve a new line break when the user hits the "Enter" key using HTML and JavaScript

I'm in the process of developing a Chrome Extension that allows users to create a to-do list. My goal is to enable users to submit their task by pressing the "Enter" key, which should move the task to the next line after submission. I am currently fac ...

Troubleshooting Problem with Website Responsiveness on iPhones Using Bootstrap 5

I am currently experiencing a challenge involving the responsiveness of a website I am developing, particularly when it comes to iPhones. The site utilizes Bootstrap 5 and displays correctly on Android devices and in Chrome Dev Tools. However, upon testing ...

Error Message "Alexa.create is not a valid function" encountered while using the Alexa HTML Web API on the Echo Show 10

Here is the HTML code for my custom Alexa Skill. <head> <script src="https://cdn.myalexaskills.com/latest/alexa-html.js"> </script> </head> <body> var alexaClient; Alexa.create({version: '1.0'}) .t ...

Issue encountered while attempting to host an ejs file using Express in Node.js

I'm encountering an issue while attempting to serve an .ejs file using express. The error I'm getting is as follows: Error: No default engine was specified and no extension was provided. at new View (C:\development\dashboard& ...

React Router consistently displaying IndexRoute

This is the main file for my app: import React from 'react'; import { render } from 'react-dom'; import { browserHistory, Router, Route, IndexRoute } from 'react-router'; // Components import Main from './components/cor ...

Add the Projector.js module using the Angular CLI

I've been working on a project using Angular CLI and incorporating three.js into it. I successfully imported three.js with import * as THREE from 'three';, but now I'm looking to add Projector.js as well. To include Projector.js, I adde ...

Transforming the JSON data into a text format

I have a JSON object structured like this: { "name": "ok", "country": "US", "phone": "900", "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1f70745f727e767331707c">[email protected]</a>", ...

The function you are trying to call in Node.js is not defined

Just starting out with NodeJs and trying to implement async/ series, but encountering an error: task.save is not a function Here is the code snippet I am working with: async.series([ (cb) => { Task .findById(id) ...

Acquire by Identifier - Tonic()

Currently, I am in the process of setting up a code example on tonicdev.com for my open-source React component available on Npm. Below is the code snippet that I am attempting to execute (editable live on tonicdev.com here): var React = require('rea ...

Building a contact form in Angular and sending emails with Nodemailer

Currently, I am in the process of setting up a contact form for my website. Since I am utilizing a MEAN stack, it made sense to incorporate the nodemailer module for sending emails. In order to handle this functionality, I have established an endpoint &ap ...

Develop an engaging billboard carousel using jQuery

After coming across a tutorial on tympanus, I decided to make some modifications to create a rotating billboard effect. However, something seems to be going wrong and it's driving me crazy trying to figure out the problem! $(function() { $('#ad_ ...

JavaScript Scroller that keeps on scrolling

Unique Continuous JavaScript Scroller I am currently working on a JavaScript scroller script that enables left to right and right to left scrolling on the click of next and previous buttons. The script is designed to work with 1 to 3 div elements in a con ...

Angular binding causing decimal inaccuracies

Within my $scope, I have a variable tobing that can be either 2.00 or 2.20, and I am binding it to: <span>{{datasource.tobind}}</span> I want the displayed text to always show "2.00" or "2.20" with the two last digits, but Angular seems to au ...

Determine the total quantity of colored cells within a row of an HTML table and display the count in its own

I need assistance with a table that has 32 columns. From columns 1 to 31, I am able to fill in colors by clicking on the cells. However, I now want to calculate and display the number of cells that are not colored in the last column. This task must be co ...

Troubles with showcasing user attributes in the view with ng-repeat and handle-bars in Angular.js

React.js, Express.js, MongoDB server.js: const express = require('express'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); const routes = require('./routes/index'); const users = ...

Minimize the visibility of the variable on a larger scale

I have a nodejs app where I define global variables shared across multiple files. For example: //common.js async = requires("async"); isAuthenticated = function() { //... return false; }; //run.js require("common.js"); async.series([function () { i ...

Create an additional column in HTML using Bootstrap styling

I am currently working on a CRUD form (using PHPMyAdmin for the database), developed in HTML, JavaScript, and PHP. The queries are executed using jQuery. I am facing an issue with my code - whenever I add a new column to my table, the formatting gets messe ...