【问题标题】:Multiple UVs/textures for single mesh in THREE.jsTHREE.js 中单个网格的多个 UV/纹理
【发布时间】:2018-10-24 17:51:17
【问题描述】:

我有一个使用四种纹理的 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])。

【问题讨论】:

  • 当您在此处发帖时,您的想法是您自己已经进行了大量研究。你都尝试了些什么?你到底卡在哪里了?问题是什么?它不应该以“如何”开头。 (另外,直接的答案是将你的 UV 坐标除以 2 并将四个图像组合成一个纹理。你尝试过吗?)
  • 目前,我尝试使用具有从 0 到 1 的 UV 的模型,以及单个纹理图像。但是,我正在使用极高分辨率的纹理 (16384^2),这会导致 FPS 极低。经过各种测试后,似乎这种低 FPS 是单个大纹理的函数,而不是所有纹理中的像素总数。
  • 渲染大量纹理可能有不同的方法。无论如何都没有可以渲染的屏幕,所以也许某种 LOD 系统可能值得研究。否则,这不能开箱即用。您可以尝试使用onBeforeCompile 修改您的object.material,但它很笨重。
  • 我发现具有四个不同 8192^2 纹理的四个相同网格以 30-60 fps 运行,而具有 16384^2 纹理的单个网格以
  • 因此,我希望如果我在单个网格上使用四个(或更多)较小的纹理,而不是单个较大的纹理,那么我可以实现更高的 FPS。现在,我试图弄清楚如何将这四个纹理分配给单个网格,以便它们使用 UV 坐标的扩展范围(即纹理 1 用于范围内的 UV(U = [0,1],V = [0, 1]),纹理 2 用于范围内的 UV(U = [1,2],V = [0, 1])等)。

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


【解决方案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 个纹理样本,但非常灵活。

【讨论】:

    【解决方案2】:

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

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

    要让一个对象使用多个材质,您可以将material 属性设置为一组材质(在创建过程中使用构造函数参数,或者只是在稍后阶段手动替换它)。

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

    然后,要使网格的不同部分使用适当的材料,您必须创建groups,如果它是BufferGeometry;或者如果您使用的是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 坐标 通过 -1:

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

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

    【讨论】:

    • 为什么不只做四个网格?
    • @pailhead 从技术上讲,可能没有区别,但是根据代码中网格的创建、存储和使用方式,拥有一个网格对象可能更方便。但是,问题在于使用具有多种材料/纹理的单个网格。
    • 这是有道理的,但我认为这是一个值得理所当然的假设。从概念上讲,这与拥有四个网格或多或少相同,但如果它在渲染之前只转换一次,而不是四次,则可能会更有效。我不认为这个问题尽可能清楚,当我想到多个纹理时,我通常会想到某种叠加,这是一个非常具体的要求,也许编辑这个问题会有所帮助稍微说清楚一点?
    • I have a THREE.Mesh with the CORRECT geometry
    • 我也不完全确定偏移示例是否可行,但我知道我之前读过它并且它是有道理的。如果你在域 [1,2] 中有一个 uv 坐标,它与在 [0,1] 中的坐标不同吗?在一个或两个方向上将它偏移 1,-1 应该只是将它带到它开始的地方?一个带有它的 uvs 矩形 [1,1,2,2] 的网格将与 [0,0,1,1] 相同。但是我对所描述几何的理解使这令人困惑。这一切听起来都与纹理图集相反。
    【解决方案3】:

    我有一个具有正确几何形状的 THREE.Mesh(UV 坐标为 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,使用它们自己的纹理。

    也可以通过使用一系列材料和设置组来实现其他答案。这就是你所需要的。当它渲染 uvs 都在 1,1,2,2 中的网格时,将与它们在 0,0,1,1 中完全相同。

    【讨论】:

    • 您对我的问题的理解是正确的。我也可能只有 4 个不同的网格。四个 UV 象限(以 (1, 1) 为中心)在功能上是不同的,因此理论上将其分成四个网格应该没有问题。但是,问题出现在渲染过程中。直观的感觉是,四个网格之间共享一些顶点以使它们看起来具有凝聚力。但是,它们没有明确连接。
    • 原因是我对网格进行了有损压缩。使用自定义算法,我可以将几何文件大小降低 >1000 倍。但是,这也会导致精度损失,这意味着“相同”的顶点没有相同的值。因此,您有闪烁。 jsfiddle.net/v311zxj2/1
    • 使用单个网格,即保留哪些顶点是“相同的”,可以这么说,允许我在绕过这个问题的同时使用我的压缩算法。
    • 我需要这种压缩 b/c 几何文件大小 >100 MB,这不可能通过网络浏览器轻松交付。完全避免精度问题的唯一方法是使用无损压缩,这已经通过 GZIP 进行,最多可节省约 40 MB。使用有损压缩,我可以将文件大小降至 100 KB 以下。
    猜你喜欢
    • 2018-07-14
    • 2013-04-23
    • 2013-10-12
    • 2014-12-22
    • 2017-06-10
    • 1970-01-01
    • 2014-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多