THREE.js中单个网格的多个UV /纹理

时间:2018-05-15 01:13:27

标签: javascript 3d three.js textures uv-mapping

我有一个使用四个纹理的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])和虚拟占位符材质。我目前加载一个像这样的纹理:

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添加多个素材。此外,我不知道如何指定网格,tex_u1_v2.png,例如,应该用于范围内的UV(U = [0,2],V = [1,2])。

3 个答案:

答案 0 :(得分:2)

Three中的标准材质只接受单个texture对象用于各种map - 参数(并且纹理对象只能容纳单个图像),因此为了使用多个纹理您的对象必须使用多种材质创建自己的多种纹理材质。 如果您有使用着色器编程的经验,您可能会使用后一种方法获得最佳性能(假设您有足够的视频内存用于大型纹理),因为您可以在单个绘制调用中绘制整个网格而无需加载新着色器或纹理。

要创建自己的着色器,您可以使用ShaderMaterialRawShaderMaterial,为每个需要的纹理添加一个纹理uniform( case)然后在着色器代码中根据坐标选择正确的样本。

要使对象使用多个材质,您可以将material属性设置为材质数组(在使用构造函数参数创建期间,或者只需在后期)。

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

然后,为了使网格的不同部分使用适当的材料,如果它是BufferGeometry,则必须创建groups;如果您使用Geometry,请设置面的materialIndex。材料索引(在组和面中)是上面显示的mesh.material数组中材质的索引。

现在您拥有不同材质的网格的不同部分,您可以为每种材质提供自己的纹理。

  • 获取正确的uv坐标的最简单方法 纹理只是将每个部分保持在[0,1]间隔中。以来 网格的每个部分都使用您不必担心的独特材料 关于重叠坐标。

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

  • 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坐标 by -1:

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

这是一个演示这些方法的jsfiddle的链接: https://jsfiddle.net/xfehvmb4/

答案 1 :(得分:0)

  

我有一个具有正确几何形状的THREE.Mesh(使用UVscords,使得U = [0,2],V = [0,2])

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

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

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

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

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

答案 2 :(得分:0)

@ 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个纹理样本,但非常灵活。