oframe / ogl

Minimal WebGL Library
https://oframe.github.io/ogl/examples
3.78k stars 214 forks source link

Duplicate GLTF object #219

Open skymen opened 6 months ago

skymen commented 6 months ago

Hi,

I'm still figuring out how to use OGL for my project, and I have a little perf test running where I load a GLTF model and add it multiple times into the scene. My main issue is it's not clear from looking at examples or reading through the code how to duplicate the gltf object given by the GLTF loader.

Right now I have it set to load the GLTF multiple times which doesn't feel good at all since a lot of heavy work is done repeatedly.

In the examples, there is GPU Instancing, but I don't really want the models to be GPU instanced, as I want separate control over each one. Ideally, the GLTFLoader would give back a reusable non Transform class that I can reuse everywhere like how geometry and programs can be shared accross Mesh classes

What is the best way to do this?

pvdosev commented 6 months ago

The GLTF loader outputs regular Mesh objects. You can copy a Mesh object by making a new one and passing in the geometry and program from the original:

new Mesh(gl, {oldMesh.geometry, oldMesh.program});

(This is assuming you want the exact same material. The Mesh objects from the GLTF loader come without a program by default, I'm assuming you're doing something like the GLTF example and making a program after loading)

If you wanted to copy an entire scene graph, you could write a recursive function that walks down the tree and does the same Mesh copying for every object and assigns it to the copy of the parent. For an exact copy, you'd also assign it the same position, rotation and scale, which you can get from the matrix decompose() function. (If you want them to move separately the position rotation and scale can't be assigned directly)

The objects may look complicated but that complexity is managed by the classes, so you don't need to worry about it too much, the geometry and program are really the only things that matter for a Mesh. Finally, I haven't checked, but every time you load the GLTF separately that data will most likely get reuploaded to the GPU. Loading a big scene over and over will probably be bad for performance, when compared to copying the objects and reusing the mesh and program data.

skymen commented 6 months ago

Thanks for the answer!

This is more or less what I did with this function I presume:

function duplicateNode(node, parent = null) {
  const { Mesh, Transform } = globalThis.OGL;
  let newNode;
  if (node.program)
    newNode = new Mesh(node.gl, {
      geometry: node.geometry,
      program: node.program,
    });
  else newNode = new Transform();

  newNode.position.copy(node.position);
  newNode.scale.copy(node.scale);
  newNode.rotation.copy(node.rotation);

  node.children.forEach((child) => {
    const newChild = duplicateNode(child);
    newChild.setParent(newNode);
  });
  return newNode;
}

I think the main source of my confusion is that: 1- I expected position/rotation data to be relative to the parent's data, and thus changing parents would just move the object around 2- I expected the GLTFLoader to give back an instance of Geometry rather than a Mesh

I suppose the reason why the loader works that way is because GLTF can store entire scenes and loading those one would expect to get all the transform data already set up.

In that regard, is is possible for the model being loaded to have multiple Mesh children that I would then have to recursively duplicate?

Also (although slightly outside of the scope of the request) do you think it would make sense to add that duplication feature to the transform class? Or make it so GLTF Loader gives back a new type of class that could be used as a geometry/program reference by a Transform class without worrying about the object's transform data.

i.e.

let car = loadGltfModel("myCar.gltf")
console.log(car instanceof Transform) // false
function createCar() {
  let object = new GltfMesh(gl, {gltf: car})
  console.log(object instanceof Transform) // true
  return object
}

Although, I do not know much about the specificities of the GLTF format and why something like this might not work without either making false assumptions about the data stored in the GLTF, or without doing some heavy data processing to make it work well.

In an ideal world however, this could then be expanded to support more model formats, and completely decorrelate the Loader classes from the Transform classes, and this hypothetical GLTFMesh class could just become some kind of general "data sourced from elsewhere" class, and "elsewhere" could be a file but it could also be something else like another scene

pvdosev commented 6 months ago

I'm a bit confused. You say you expected position/rotation data to be relative to the parent's data, but it is. If you change the position/rotation of a parent, it'll move the child. You can do this with nodes straight from the gltf loader. Are you sure your objects are parented correctly in whatever 3D editor you're using? And yes, a gltf file will store an entire scene, including parenting relations and such. (Also cameras, lights, curves, a bunch of stuff)

You can access the geometry directly, but there's a bit of indirection. There is a small mismatch between the gltf format and the ogl objects: a gltf mesh can have multiple primitives, and an ogl Mesh can only have a single geometry. That's why the ogl gltf importer "double-wraps" the meshes, there will be an extra Transform parent for every Mesh so that they can be grouped together, in case there are multiple gltf primitives.

If you look in gltf.meshes in the output from the gltf loader, you'll see an array of Transforms. They have all the Meshes as direct children, which contain all the geometries you might want. You can recombine those into Meshes however you want.

You can make all the abstractions you want! I don't exactly follow what you want to do, but there's no technical limitation to stop you. If you want to change things to that extent, it might make sense for you to copy the gltf loader code and only keep the parsers for the data you want. But it sounds to me like you're fighting the data format instead of working with it. For the game engine I'm working on, I'm using gltf files as a level format, and I'm adding a bunch of custom data for physics colliders and game properties to the gltf nodes. Also, making something super abstract might make it less useful.

skymen commented 6 months ago

I'm a bit confused. You say you expected position/rotation data to be relative to the parent's data, but it is. If you change the position/rotation of a parent, it'll move the child.

By that I meant that when I changed a node's parent, it seemed to not behave the way I assumed it would, but also it might be due to me not setting things up properly. Earlier you mentionned this which was interesting information for me

For an exact copy, you'd also assign it the same position, rotation and scale, which you can get from the matrix decompose() function. (If you want them to move separately the position rotation and scale can't be assigned directly)

I would need to look into this.

You can make all the abstractions you want! I don't exactly follow what you want to do, but there's no technical limitation to stop you.

You're right! I'm still learning how OGL works, so seeing how you use the engine and how you reason with it helps me understand how the engine works, so thank you for your time. Thank you for all the explanation about how Mesh, Geometry and the GLTF Loader works.

For the game engine I'm working on, I'm using gltf files as a level format, and I'm adding a bunch of custom data for physics colliders and game properties to the gltf nodes.

May I ask if you would be ok sharing that system? This sounds similar to one of the things I am trying to do, and understanding how you've implemented it would probably help a lot. Understandable if you do not want to share that code though 👍

pvdosev commented 6 months ago

I'll try to remember to post it when it's done, but it's too in-development to show off still, it's not very functional. I use Blender as my game editor, and then use a blender plugin to add custom properties and extensions to the gltf file. Let me know if you wanna chat about game dev though!