THREE.js 中单个网格的多个 UVs/textures

Multiple UVs/textures for single mesh in THREE.js

我有一个使用四个纹理的 OBJ。文件中定义的 UV 范围从 (0, 0) 到 (2, 2),例如 (0.5, 0.5) 指的是第一个纹理中的坐标,(0.5, 1.5) 是第二个纹理中的 UV 坐标, (1.5, 0.5) 是第三个纹理中的坐标,(1.5, 1.5) 是最后一个纹理中的坐标。

我已经有了正确的 three.js 几何图形或对象。但是,我现在需要能够将正确的纹理贴图应用到这些对象。

在代码中:

我有一个 THREE.Mesh 具有正确的几何形状(UV 坐标使得 U = [0, 2],V = [0, 2])和一个虚拟占位符 material。我目前像这样加载单个纹理:

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

正如预期的那样,四分之一的网格纹理正确。我还有三个纹理文件,tex_u1_v2.pngtex_u2_v1.pngtex_u2_v2.png。我希望能够将这些纹理也应用到 object(THREE.js 网格),这样网格中的每个有效 UV 都有一个纹理。

但是,我不知道如何在 object 创建后将多个 material 添加到 object。此外,我不知道如何向网格指定 tex_u1_v2.png,例如,应该用于范围内的 UV (U = [0, 2], V = [1, 2]).

Three 中的标准 materials 仅接受单个 texture object for the various map 参数(并且纹理对象将仅包含单个图像),因此为了在对象上使用多个纹理您将不得不使用多个materials创建您自己的多纹理-material。 如果您有着色器编程经验,您可能会使用后一种方法获得最佳性能(假设您有足够的显存用于大型纹理),因为您可以在单个绘制调用中绘制整个网格,而无需加载新的着色器或纹理。

要创建自己的着色器,您可以为每个需要的纹理使用 ShaderMaterial or RawShaderMaterial, give it one texture uniform(在您的情况下为四个),然后在着色器代码中选择正确的一个根据坐标采样。

要使一个对象使用多个 material,您可以将 material 属性 设置为 [=69= 的数组]s(在使用构造函数参数创建期间,或者只是在稍后阶段手动替换它)。

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

然后,要使网格的不同部分使用适当的 material,如果您使用 Geometry,则必须创建 groups if it is a BufferGeometry; or set the materialIndex 个面。 material索引(在group和face中都有)就是上面显示的mesh.material数组中material的索引。

现在您已经有了具有不同 material 的网格的不同部分,您可以为每个 material 赋予它们自己的纹理。

  • 获得正确 uv 坐标的可能最简单的方法 纹理只是将每个部分保持在 [0,1] 区间内。自从 网格的每个部分都使用独特的 material 你不必担心 关于重叠坐标。

如果您不想修改现有坐标,有两种替代方法:

  • texture wrapping 设置为 THREE.RepeatWrapping:

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

    这将使纹理重复超出标准 [0-1] uv 间隔。

  • 另一种方式是利用贴图的offset属性 将其推回 [0-1] 区间。对于要放置的纹理 u[0,1], v[1,2] 间隔你会设置偏移量的 v 坐标 通过 -1:

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

这是演示这些方法的 jsfiddle link: https://jsfiddle.net/xfehvmb4/

I have a THREE.Mesh with the correct geometry (with UVs coords such that U = [0, 2], V = [0, 2])

在这种情况下,您对 "correct" 的理解可能不正确。让我们考虑一个四面体,并将其映射,产生 4 个三角形。让我们将每个三角形放在它自己的 UV space 象限中。四个象限是:

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

我不完全确定如何最好地传达这一点,但如果有意义的话,纹理查找总是在四边形 A (0011) 中完成。一旦一个值低于 0 或高于 1,它就会环绕并查找相同的 space。所以,这里的所有 4 个三角形 (ABCD) 实际上都是重叠的。不存在超过此范围的纹理。您要么夹在边缘像素上,要么环绕(或可能镜像)。

UV 超出此范围可能有充分的理由,但对您的情况没有多大意义。我想你没有任何三角形穿过这些边界,所以你也可能只有 4 个不同的网格,它们在 0,1 域中有自己的 UV,使用它们自己的纹理。

也可以用另一个答案来实现,通过使用一系列材料和设置组。这就是您所需要的。当它渲染 uv 都在 1,1,2,2 中的网格时,它与它们在 0,0,1,1 中完全一样。

@Jave 的回答对我来说看起来很不错,但如果你想改为走 ShaderMaterial 路线,你可以这样做:

// Make the material
var material = new 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() {
          // This maps the uvs you mentioned to [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);
          // These lines make sure you get the right texture
          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;
        }
      `,
    });

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 });

这有点低效,因为您正在做 4 个纹理样本,但是非常灵活。

也可以将不同类型的纹理组合为一个混搭,例如

  • 凹凸贴图
  • 扩散
  • 法线贴图 ...等等

我下载了@types/three and it helped me a lot to understand properties of MeshPhongMaterial, which instantiated my mesh material. There are props bumpMap, map, normalMap等。还有一个 color 属性 可以改变纹理的颜色。

在尝试调试代码一周后,我找到了这个解决方案:

  1. 如果加载了 3D 对象,则在任何 3D 软件(例如 blender)中将其 uv 设置为 Modal,这将在三个代码中为对象添加更多网格并导出。然后加载Threejs.

  2. 您可以通过控制台记录 3D 对象来获取 Mesh 详细信息:

    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. 然后把UV贴图替换成想要的uvmap,我把uv2赋值给uv,截图如下:
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)