Search code examples
performance3dthree.jstextures

three.js mesh with many textures


I'm currently trying to create a three.js mesh which has a large number of faces (in the thousands) and is using textures. However, my problem is that each face can have its texture changed at runtime, so potentially it's possible that every face has a different texture.

I tried preloading a materials array (for MeshFaceMaterial) with default textures and assigning each face a different materialIndex, but that generated much lag.

A bit of research led to here, which says

If number is large (e.g. each face could be potentially different), consider different solution, using attributes / textures to drive different per-face look.

I'm a bit confused about how shaders work, and in particular I'm not even sure how you would use textures with attributes. I couldn't find any examples of this online, as most texture-shader related examples I found used uniforms instead.

So my question is this: Is there an efficient way for creating a mesh with a large number of textures, changeable at runtime? If not, are there any examples for the aforementioned attributes/textures idea?


Solution

  • Indeed, this can be a tricky thing to implement. Now I can't speak much to GLSL (I'm learning) but what I do know is Uniforms are constants and would not change between calls, so you would likely want an attribute for your case, but I welcome being wrong here. However, I do have a far simpler suggestion.

    You could use 1 texture that you can "subdivide" into all the tiny textures you need for each face. Then at runtime you can pull out the UV coordinates from the texture and apply it to the faces individually. You'll still deal with computation time, but for a thousand or so faces it should be doable. I tested with a 25k face model and it was quick changing all faces per tick.

    Now the trick is navigating the faceVertexUvs 3 dimensional array. But for example a textured cube with 12 faces you could say reset all faces to equal one side like so:

    for (var uvCnt = 0; uvCnt < mesh.geometry.faceVertexUvs[0].length; uvCnt+=2 ) {
        mesh.geometry.faceVertexUvs[0][uvCnt][0] = mesh.geometry.faceVertexUvs[0][2][0];
        mesh.geometry.faceVertexUvs[0][uvCnt][1] = mesh.geometry.faceVertexUvs[0][2][1];
        mesh.geometry.faceVertexUvs[0][uvCnt][2] = mesh.geometry.faceVertexUvs[0][2][2];
    
        mesh.geometry.faceVertexUvs[0][uvCnt+1][0] = mesh.geometry.faceVertexUvs[0][3][0];
        mesh.geometry.faceVertexUvs[0][uvCnt+1][1] = mesh.geometry.faceVertexUvs[0][3][1];
        mesh.geometry.faceVertexUvs[0][uvCnt+1][2] = mesh.geometry.faceVertexUvs[0][3][2];
    }
    

    Here I have a cube that has 6 colors (1 per side) and I loop through each faceVertexUv (stepping by 2 as two triangle make a plane) and reset all the Uvs to my second side which is blue. Of course you'll want to map the coordinates into an object of sorts so you can easily query the object to return and reset the cooresponding Uv's but I don't know your use case. For completness, you'll want to run mesh.geometry.uvsNeedUpdate = true; at runtime to see the updates. I hope that helps.