thinInstance multiple materials

Here’s a PG that should do what you want:

https://www.babylonjs-playground.com/#YAWQ2W#48

@Dshah_H, I think that @Evgeni_Popov is right in that fixing the UVs at the mesh source is the most optimal solution. If you want to map your atlased texture to a part of your mesh, there are a couple of things you should do. This is agnostic of any DCC tool, so you can do this in any of them.

To keep the UV sets clean and easy to work with I would create a separate UV set so that you don’t have textures colliding in your UV space. On that second UV set, you will map your panel in one of two ways depending on the format of your texture. If you are using a non-square texture for your atlas, you will map your UVs to the full 0-1 space which will distort the texture, but when applied to the mesh which is the correct proportion, the mesh will correct any stretching in UV space:


Assume this is your texture and each iteration in the atlas is at the proportion of the panel in your mesh. You would then lay out your UVs for the panel like this:


With the grid being the entire 0-1 of UV space and the blue line being your UV edges for the panel.

If you needed to create a square texture, you will end up changing the proportion of the artwork that needs to be applied to the panel. To prevent any distortion of the artwork, you will need to create the texture with extra padding on each side so that slicing the panel proportion out of the center will yield no distortion:

Assume each square in this atlas needs the type to not be distorted, but we have extra padding in the background color to act as a “bleed” for the texture. The UVs then retain the proportion of the original panel so that you eliminate any distortion to the art:

You can see that in this case the blue line of the UV edge cuts out the center of the texture tile and will eliminate any unneeded pixels to retain the correct proportion to the mesh panel.

Obviously the non-square texture is more optimal since we aren’t carrying around the extra pixels that are unnecessary for the mesh proportions leading to a smaller download while still being able to use the same offsets in UV space for the atlas to work.

I hope this clears up a bit about how you would UV for that panel to tile, but feel free to ping me back if anything is unclear.

2 Likes

@PatrickRyan I have forward this to 3d artist for model, and also, I will be creating texture using imagemagick lib in near future, so it will be a great help for me to use this same technique in my code, if I have a question I will surely inform you <3, I am so amazed to see this kind of support, Thank you.

@Evgeni_Popov that’s super helpful, I would be grateful if you could please let me know few details about your code, as I want to understand more logic behind below lines, I have added comments where I need to understand :

for (let vv = 0; vv < uvs.length / 2; ++vv) {
     //?????? How you knew this location needs update to flip the mesh? and how exactly it flips?
    let u = uvs[vv * 2 + 0], v = uvs[vv * 2 + 1];
    uvs[vv * 2 + 0] = 1 - u;
}

and

//?????? what are these 10000 values?
let umax = -10000, umin = 10000, vmax = -10000, vmin = 10000;
for (let vv = 0; vv < uvs.length / 2; ++vv) {
   //?????? How you knew this location needs update and what exactly it does?
    const u = uvs[vv * 2 + 0], v = uvs[vv * 2 + 1];
    umin = Math.min(umin, u);
    umax = Math.max(umax, u);
    vmin = Math.min(vmin, v);
    vmax = Math.max(vmax, v);
}
const urange = umax - umin, vrange = vmax - vmin;

for (let vv = 0; vv < uvs.length / 2; ++vv) {
    //?????? What are these min/max/range
    let u = (uvs[vv * 2 + 0] - umin) / urange, v = (uvs[vv * 2 + 1] - vmin) / vrange;
    uvs[vv * 2 + 0] = u;
    uvs[vv * 2 + 1] = v;
}

also, the PG you see I have to create like 200 stands, which means 4 pics (with 64x64 pixels or maybe 128x128 or maybe larger) multiply by 200 stands, so almost 800 tiles. A very large texture, is there something special I should be watching for performance? like size of texture etc?

Thanks

1 Like

//??? How you knew this location needs update to flip the mesh? and how exactly it flips?

It’s the object where the texture was flipped (idx == 16 || idx == 26). The flipping was either on the u or v coordinates, so I tested both. It happens it is the u coordinate that needs to be flipped, hence 1 - u (the texture coordinates range from 0 to 1).

//??? what are these 10000 values?

It’s some code to compute min and max values. I set min to a big value to be sure it will be overwritten by the very first texture coordinates (as they range from 0 and 1) and max to a very small value for the same reason.

//??? What are these min/max/range

This code is meant to scale the u/v coordinates from the [min,max] range to the [0,1] range. To do so, juste do (u - umin) / (umax - umin) as u can go from umin to umax (same thing for the v coordinate).

A 1856x1856 atlas texture will let you do what you want. Indeed, 1856/64=29, so you can fit 29*29=841 64x64 tiles in this texture. This means your device should support textures up to 2048x2048, so it depends on your target(s).

1 Like

@Evgeni_Popov Thanks, Thats great information for uvs, now its starting to make sense to me.

I will have visitors from any device mobile/pc/tablet, what do you suggest in this case?

No idea, I don’t know what the minimum texture sizes those devices can support…

If you want to be on the safe side, you will need to make multiple atlas textures (4 x 1024x1024? However, I don’t know if 1024 is a minimal texture size you can rely on…).

1 Like

That’s exactly what I was thinking, I will go with 4 * 1024x1024 and if it creates some issues I can easily split stands in 100x2 or even 50x4.
That snippet you gave me to edit uv is lifesaver <3

1 Like

@Evgeni_Popov I am having issue when using createInstance, scene takes huge CPU usage and FPS is dropping very low, I think thinkInstance can help here. I wanted to ask if we can also apply custom texture to thinInstances, for reference this is working perfectly for createInstance https://www.babylonjs-playground.com/#YAWQ2W#50

But the same when I try to apply on thinInstance method make the meshes disappears with custom material.

I am sure this we don’t need to use in second PG:
e.instancedBuffers.tileOffset = getTileOffsetData(tileX, tileY);

since I will be doing something like:

    e.thinInstanceSetBuffer("matrix", bufferMatrices);
    e.thinInstanceSetBuffer("tileOffset", bufferMatricesTile);

but don’t understand where I will save those bufferMatricesTile
https://www.babylonjs-playground.com/#YAWQ2W#56

EDIT: I was able to display first stand by retuning array instead of Vector4 from function getTileOffsetData, still struggling to repeat on all stands.
https://www.babylonjs-playground.com/#YAWQ2W#59

Edit 2: I am getting closer, but still some textures are missing
https://www.babylonjs-playground.com/#YAWQ2W#60

Edit 3: I was able to show all textures by increasing buffer size bufferMatricesTile = new Float32Array((4 * 7) * totalStands); // 7 is the number of meshes which needs material. but they show same region still
https://www.babylonjs-playground.com/#YAWQ2W#61

I was able to apply texture properly on first stand by setting:
bufferMatricesTile.set(getTileOffsetData(tileX, tileY),0);
but in the loop I have issue, somehow I need to set startBufferTile on line 131, right now its only showing first letter from texture on each stand:
bufferMatricesTile.set(getTileOffsetData(tileX, tileY),startBufferTile);
https://www.babylonjs-playground.com/#YAWQ2W#62

EDIT:
Finally fixed it: https://www.babylonjs-playground.com/#YAWQ2W#63

2 Likes

@Evgeni_Popov I have this feature request, if this can be included in Babylon to support a new material after X instances that would be a huge boost,

as you see in above PG ( this PG has second material https://www.babylonjs-playground.com/#YAWQ2W#66 ), after every 60th and 120th stands I am moving stands to new positions

            // 3rd/4th rows of stands
            if (i == 59) {
                startingX = -2390;
                startingXRotated += -2230;
                distanceValueZUnrotated = 0;
                distanceValueZRotated = -6500;
            }

            // 5th/6th rows of stands
            if (i == 119) {
                startingX = -4530;
                startingXRotated += -2250;
                distanceValueZUnrotated = 0;
                distanceValueZRotated = -6500;
            }

on those stands I want to replace the material of mesh, which isn’t possible right now with Babylon, so to overcome this I divided 180 stands in 3 separate calls, I loaded same model 3 times with 60 stands in each call, and apply different material (same image for testing):

assetsManager.addMeshTask('stand1', '', './assets/stand/', 'stand25.obj').onSuccess = (task) => {
let totalStand = 60;
.....
assetsManager.addMeshTask('stand2', '', './assets/stand/', 'stand25.obj').onSuccess = (task) => {
let totalStand = 60;
.....
assetsManager.addMeshTask('stand3', '', './assets/stand/', 'stand25.obj').onSuccess = (task) => {
let totalStand = 60;
.....

in both cases my FPS remains at 60, and draw calls with 180 stands is at 184 but with 3X60 approach draw calls rate increased to 324.

Thanks <3

1 Like

We can’t change the material when rendering thin instances, for this to be fast it has to be the same material (else it’s not thin instances anymore).

What you can do is cloning the master stand 2 times to save on the memory used for geometry compared to loading your mesh 3 times, but you won’t be able to avoid doing 3 times the number of draw calls.

If you really want to minimize the number of draw calls you should divide your stand mesh into two meshes: one mesh A with all the parts that don’t need a material and one mesh B with the parts that need a material.

You would only need 4 draw calls to draw your stands: one draw call for A and one draw call for B for each different materials.

1 Like

right, I am going to do A,B meshes method to minimize draw calls, as it seems more reliable and challenging. and, I really can’t thank you enough for this all information. I am finally able to finish the whole scene just because of you. just a last request please, I am trying to replace previous texture with 1024X1024 but encountering issue, trying from last few hours to figure this out but my mind has stopped working on math here, since this texture has 10 columns instead just 4 I am trying on how to lay UV like previous PG:

                tileY = (tileWidth * 3) - (i % 4) * tileHeight;
                tileX = (tileWidth * 3) - (i % 4) * tileWidth;

https://www.babylonjs-playground.com/#YAWQ2W#67

It can’t work, 1024/100=10.24 is not an integer. It means all the tiles can’t have the same size, which is a requirement for the atlas to work.

You need to have all your tiles the same size. Either make tiles 64 pixels or 128 pixels, which means 16 or 8 tiles per row.

1 Like

:bowing_man: ah right, ok… I am waiting for the correct texture and thought to try this I found online, and just for testing if the code is right, since the image is 1024X1024, now I tried with 128 pixels, but due to math issue I am not able to show different region on next stands, at least it should show next region

https://www.babylonjs-playground.com/#YAWQ2W#68

so definitely is some issue with code too.

I create a quick texture to try, at stand 3 (9-0) it starts again from 1 instead going forward.
https://www.babylonjs-playground.com/#YAWQ2W#69

I think it’s what you want:

https://www.babylonjs-playground.com/#YAWQ2W#70

1 Like

@Evgeni_Popov I LOVE YOU sir, thank you so much, I actually updated your snippet https://www.babylonjs-playground.com/#YAWQ2W#71 this solved the problem for draw calls to remain at 184 without splitting my model, now I just create 4 materials and apply to all 180 stands

Thank yoOoOoOoo a lot

2 Likes

I’m trying to get this example to work using thinInstanceRegisterAttribute and thinInstanceSetAttributeAt

The lines I changed are at around 191 and 216 (I also commented out 254). Can anyone see what I’m missing here? Thanks

https://www.babylonjs-playground.com/#YAWQ2W#72

I just figured out that I’m using the wrong index. Maybe this isn’t the best example to use for my problem. I’ll start a new thread with another example PG.

Edit:
I got it to work here:


I need to investigate further to see why this isn’t working in my project.
3 Likes