[OPEN SOURCE] Multiplayer 3D RPG Using Colyseus

Thanks @Joe_Kerr I appreciate you trying,

I may have found something, If I toggle this setting for the material, the black issue dissapears, does this make sense to anybody?

this would usually highlight a z-fighting issue I would guess ? @Evgeni_Popov ?

Yes, it could be that another (black) sword is rendered after the regular sword. Thanks to the “need depth pre-pass” flag, the second rendering would not occur…

Note that I could not reproduce the black sword with your link: I bought the sword, equipped it, but it’s not showing black (?).

1 Like

Hi there,

If you want, I’ve removed the temporary fix for the black item on the demo: https://t5c.onrender.com/

  1. Use keyboard 5 to generate an item on the floor
  2. User keyboard J to remove all other entities to remove clutter
  3. SHIFT + CTRL + ALT + I to open inspector

@oriongu, I had a hunch so I dug into your merged sword asset and validated my hypothesis. The normals of your vertices are inverted so they all point inward. It’s most obvious in the handle as the vertices on the blade are at the edge so knowing which of the split vertices is rendering each normal line is not that clear.

They should be pointing out for the lighting calculations, but you have them pointing in which means the back faces, while rendering, are not lit by the scene lights.

If you just reverse the normals on your mesh, it should render correctly. Hope this helps but ping back with more questions.

1 Like

Apart from the normals, I can see with Spector that the albedo texture is not passed to the shader when drawing the sword. After some testing, I could see that using a standard material for the sword seems to work, but using a PBR material doesn’t because of this albedo texture being not set. I also noticed that enabling transparent rendering for the (PBR) material makes it work too: set alpha=0.99 and transparency mode=Alpha Blend, for eg.

I don’t know why that happens, though… cc @sebavan in case it rings a bell.

1 Like

nope, nothing I can think of but some kind of early freezing ?

There are some mutual exclusive properties that will be automatically ignored when certain values are 1.0 or 0.0. Like, if metallic is 1, transmission is ignored. Further complicating it, I think some extensions can make previously mutually exclusive properties both have contribution to the rendering equation. My mental model for exclusive gltf props is a jumble, so i have little to contribute to the discussion beyond this, but it feels like it could be one of these cases.

1 Like

Hi Patrick,

Thanks for that, unfortunately, doesnt seem to make any difference, even after “fixing” the normals:

The only fix I have found that works is adding this to the material:
selectedMaterial.needDepthPrePass = true;

I’m not sure if that has an impact on the performance though?

needDepthPrePass = true does have an impact on perf as it renders first the object into the depth buffer, but it shouldn’t be so bad as your object has few faces and you don’t have thousands of them.

1 Like

Hi there,

Trying to improve performance, and implementing VAT bit by bit.

performance is ok with 3 type of entity

with 1 entity type, performance is amazing

and with retargeting animation for the equipment

EDIT 1: I’m very close to having a similar structure on my debug scene vs my game scene, so implementation should be very simple, but before that still figuring out to prevent certain animations from looping.

4 Likes

Hi @Alexander_Sosnovskiy,

Before I proceed in fully implementing VAT to my project, I would like to explore all options. You previously suggested I could attach equipment to my instances using bone weights, would you happen to have an playground showing how this could be done?

This is the context: https://playground.babylonjs.com/#3NIXCL#56

Many thanks,

Let me explain it as simple as possible. It means in the real code the things more complicated!

NOTES!!!

  1. I oversimplify most things so forgive me for that.
  2. I only use the “position” attribute in my explanation. We also have rotation and scale, but I’ll omit it.
  3. I remove some useless code like creating additional skeletons …

We have a character mesh. It contains vertices. Each vertex contains attributes like position, uv etc.

If we have only vPosition(position of vertex), BJS renders it “as is”.
Before render, BJS calculates final vertex position. Because we have “node.position” too, the “unreal code” looks like:

vertexFinalPosition = node.position * vPosition

We don’t need to do it manually, it works under the hood.

Now we want to animate the vertex. We can manually change every vertex’s position like vPosition = some new value But it’s boring… So, we’re starting to use the Skeleton.

The Skeleton is a simple bunch of bones(invisible vertices with positions) which BJS respects in calculation of vertexFinalPosition

Now we need to specify for every vertex which bone and how much we want to apply it(that is the reason of using matricesIndices and matricesWeights attributes for every vertex).

vertexFinalPosition = node.position * vPosition * bonePosition * boneWeight

As you can see, we can manipulate vertex’s position by changing bonePosition. That’s how Skeleton animation works.

The animation is the sequence of position changes(yeah, not only position :exclamation:). and we apply it to bone! So the bone’s position changes via animation.

BUT! If we have a lot of skeletons and bones, we need to calculate every bone per every animation frame on CPU!

VAT allows us to move calculation from CPU to GPU. The animation texture is a sequence of bone’s position changes(not only position :exclamation:) in the pixels form.

bonePositionFromVAT = .. get bone position from texture using boneIndex and time!
vertexFinalPosition = node.position * vPosition * bonePositionFromVAT * boneWeight

So we can run this code on the GPU. The only things we need to sync between CPU and GPU are node.position and animation time.

Now how does the code work?

As you can see from example above, we can animate the mesh using the skeleton.

Now we have the character with the skeleton(+ animation).

We want to animate the sword too. So we can apply the skeleton animation to the sword’s mesh.

But we have an issue. Because the sword is a separate object, we need to “fix” the position for every sword’s vertices because we want to see sword in the hand and not between foots. That’s why we have 73 - 75 lines in the code before merging the sword. We “fix” the sword position(and it automatically fixes all vertices positions). I think the best solution is applying such transform in Blender.

122 - 138 lines: re-using VAT and set matricesIndices and matricesWeights attributes for every sword’s vertex;

149 - 161 lines: we need to move sword’s object in the same place as the character’s object + add VAT for every instance;

UPDATE

Sorry. I forgot to explain the difference between “baked” vertex position VS item slot ways.

“baked” vertex position

That’s how character model works. You prepare you model in Blender, attach every vertex to bone and done! It works great for character model, helms, boots, glovers, armor. Because you can manually change the position of each armor for example. Like Dragon Armor should be a little bit higher than Leather Armor. You don’t want to have separate item slot for that or have a lot configurations code like dragonArmorFixYPosition = 0.5. So, you bake all fix fixes into mesh.

item slot

Item slot works great if you have more than one item slot for your item. Like Swords! You can attach a sword to LeftHandSlot or RightHandSlot. So you place your sword in Blender in right place with right rotation(it is a separate topic about content preparation…). But the final position will be calculated via code. (73 - 75 lines in my code). Like finalPosition = meshOriginal * boneOriginalPosition

For Swords, you can also use Baked position “as is” for LeftHand and apply sword.position.x *= -1 to sword for RightHand (flip it horizontally).

UPDATE 2
Use can disable multiMaterial for MergeMeshes (set last parameter to false) to have 3 draw calls instead of 8.


@sebavan @Evgeni_Popov please correct me if I’m totally wrong.

1 Like

That’s great @Alexander_Sosnovskiy!

You can even simplify the PG, as we don’t need the additional skeletons / animation groups anymore.

Now that the sword is handled as a VAT, we can also re-enable the offset to desynchronize the animations:

1 Like

Yeah, I’ve removed it too! Thanks! @oriongu please use @Evgeni_Popov 's PG because its simpler and with random offset turned on!

I think the performance budget will hurt if you want to customise your character a lot!

Simple example:

We have 100 characters on the screen. DC = 100;

We have 5 customisable parts for every character: DC = 100 + 100 * 5 = 600;

600 DCs only for characters render!

Can we improve it? Yeap! But is it more about content optimisation!

We can merge items geometries into one mesh and then hide/show some vertices. I use it in my game. The disadvantage is we need to process every vertices in our vertex shader. So if you have ~500verts per item, and 10 items sets with 5 items in every = 50 items * 500 verts = 25 000 vertices per one character!

100 characters = 25k * 100 = 2,5m!

What we want? I think it is ok to have 2 DCs per character. So 100 characters = 200 DCs.

So we can merge all items into single meshFromItems per character. We can event merge it with the character itself!

So its up to your to pick right tool and way to handle 100 characters with items!

Sorry guys! I have an idea of “best possible” solution for character+items.

@Evgeni_Popov I need your opinion.

So in general, in MMORPG games the most frequently changed values are vertices positions(via character’s position or animation). But the number of vertices doesn’t change a lot. Like one time per second(only when player changes items on character and only in case of 100 players… For lower number of player the period should be greater than 1 second). Also, we can apply this optimization only for remote players. For the local player we can draw “as is” for better performance(because we don’t want to wait X ms while building geometry after switching items on local player)

Example:

For the current scene, we know who wears what:
Player1: character model, dragon armor;
Player2: character model, leather armor + boots;

We can build a “usage” map:
Character model: 100 times;
Dragon armor model: 50 times;
Leather armor model: 35 times;

We can build a “global” geometry = all used models (character + dragon armor + leather armor + …);

Then we need total number of vertices = charModelVertCount * 100 + dragonArmorModelVertCount * 50 …

And for every vertex we need to know which vertex from “global” geometry we need and apply position/etc attributes.

Then we can draw it in one DC!!!

So my question is Does the SolidPS work this way? An Introduction To The Solid Particle System | Babylon.js Documentation

Of course, we need to provide bone/VAT/anim data for every vertex too…

The drawback is the position for each vertex should be updated each frame. In our case the total number of vertices is 350k(100 player * 3500 vertices per charater+items). I think it will hurt a performance. Am I right?

@Evgeni_Popov how does it sound? May we use SPS to achieve this?

Yes, I think the amount of data may be a bit too much to update each frame, as the real amount is 100*3500*3*4/1024/1024=5.3Mb, because a vertex has 3 float coordinates. Also, building the buffer wil likely involve some matrix calculation, as you would have to recompute the position of each vertex, which would take a sizeable amount of time for so many vertices. As always, the tradeoff is between doing some more work in javascript or on the GPU, which depends on your scene, the CPU and the GPU. I feel that you are moving too much work on the javascript side, but the best thing would be to implement the method and compare with existing solution(s) :wink:

I don’t think the SPS would help, because if you already recompute the vertex buffer, you can directly use it for your mesh(es).

1 Like

Thanks alot for all that input, it’s all very helpfull @Evgeni_Popov @Alexander_Sosnovskiy

After having a good lock at the PG, it does feels less complex. I’ll just have to give it a try and see it it works for my case :slight_smile:

A little about performance, my original plan was to able to have 20-25 players, and 100 enemies, and once I finish integrating VAT/instances, I’ll should be able to do at least that but probably quite a bit more. In all case, more optimization can always come later: I just do not want to get bogged down and end up over-optimizing and actually never finishing the project.

In the meantime, I did work on a critical part of the project… drum roll…: added a new rat enemy to the dungeon level :stuck_out_tongue:

1 Like

This looks better and better @oriongu !!!

1 Like