-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Culling animated models. #723
Comments
Does this effectively require 1:1:1 skin-mesh-animation binding, or do you accumulate over all existing combinations? |
This is animation independent. It just uses the joint/node transforms and they can be modified manually/programmatically as well. That's part of the reason I chose this approach as opposed to storing precalculated bounds per animation key frame, which would not only consume a lot of space but also doesn't account for animation blending or manually/programmatically changing the joint transforms. There also does not need to be a 1:1 mapping between a skin and mesh. If multiple meshes use the same skin then the local mins/maxs per joint just need to include all vertices that are influenced by the joint. The downside is that if you calculate the total bounds per skin then you may end up with a larger bounds per mesh because it may include joints/vertices that do not affect the particular mesh. I don't think this is a big problem though because having multiple meshes per skin is probably not all that common and when it does happen, the meshes typically are part of the same "model" so it's fine to use a single bounds for all "surfaces" of that "model". With that said, you could store the jointGeometryMins/jointGeometryMaxs on a mesh. But then you could store the skin.jointNames on the mesh as well. The existence of "skins" seems to be to be able to have a single joint array for multiple meshes. Culling per skin also allows you to not update the joint array if the skin is not in the view frustum. This is worth considering when you use a uniform buffer to store the joint matrices (which I am). Nobody wants to limit the number of joints per mesh by the maximum number of individual uniforms. Maybe there are other good reasons to have separate skins but I'd be perfectly happy getting rid of skins and storing the relevant properties directly on a mesh. If skins are kept then it may be better to have a mesh reference a skin as opposed to having a node reference one skin and potentially multiple meshes. I also have found no use for listing "skeletons" per node but that's getting off topic. |
Please, correct me if I'm wrong: |
In the converter (fbx2gltf) I first skin each vertex just like you would on the GPU. Then for each vertex I transform the skinned vertex into the local space of the joints that influence the vertex and add these local space positions to the mins/maxs of the joints. In other words, for each vertex I do the following:
At run-time I do the following:
As you pointed out previously, you can do this per skin (which I am doing here) or you can do this for different subsets of joints. |
As I can see from the code above, current skinning indirection does more harm than good:
|
Sharing a "skin" among multiple meshes isn't necessarily bad and allows you to share a single joint array (or uniform buffer). Multiplying with the inverseBindMatrices and updating a uniform buffer isn't free after all. However, I would get rid of the "skin" and "skeletons" references on nodes and just have a mesh directly reference a skin. I would also get rid of the "jointName" on nodes and directly reference the nodes that transform the mesh. As far as I'm concerned any node can be part of any skeleton. Anyway, as for culling animated meshes, this is just one idea. While I need a solution right now, there may be other/better solutions? |
Isn't this more like an implementation issue than a spec one? In theory, Also it's not quite clear, why do we have With all your feedback, let's evaluate the following schema changes:
Here's an example: {
"meshes": {
"Cylinder-mesh": {
"primitives": [{
"attributes": {
"JOINT": "accessor_40",
"NORMAL": "accessor_20",
"POSITION": "accessor_18",
"WEIGHT": "accessor_37"
},
"indices": "accessor_16"
}],
"skin": {
"inverseBindMatrices": "IBM_accessor",
"joints": [
"Bone",
"Bone_001"
],
"jointGeometryMins": "mins_accessor",
"jointGeometryMaxs": "maxs_accessor"
}
}
},
"nodes": {
"Bone": {
"children": [
"Bone_001"
],
"rotation": [
0.70474,
0,
0,
0.709465
],
"translation": [
0, -3.15606e-007, -4.18033
]
},
"Bone_001": {
"rotation": [
0.00205211,
9.94789e-008,
0.000291371,
0.999998
],
"translation": [
0,
4.18717,
0
]
},
"Cylinder": {
"meshes": [
"Cylinder-mesh"
]
}
}
} This approach eliminates a couple of not-so-obvious issues, such as:
I can see only one (for now) major drawback here: |
You raise a very good point about playing different animations on different instances of the same mesh. The logical thing to do would be to use two separate sub-trees and two skins. However, that means you cannot reference the skin from the mesh. So maybe we should list a (mesh,skin) pair on a node? Note that this is singular. Do we need to be able to list multiple meshes on a single node? Afaik the FBX format doesn't support this for instance. Every node has a single attribute (mesh, camera, light etc.) |
Sorry for the delay,
The only use case I can think of: "augmentation" of a mesh with other mesh (character outfit upgrade?) Of course, it could be replaced with one more node.
If we move So maybe we should move them out of mesh too: {
"meshes": {
"Cylinder-mesh": {
"primitives": [{
"attributes": {
"JOINT": "accessor_40",
"NORMAL": "accessor_20",
"POSITION": "accessor_18",
"WEIGHT": "accessor_37"
},
"indices": "accessor_16"
}]
}
},
"skins": {
"skin_01": {
"inverseBindMatrices": "IBM_accessor",
"joints": [
"Bone",
"Bone_001"
]
}
},
"nodes": {
"Bone": {
"children": [
"Bone_001"
],
"rotation": [
0.70474,
0,
0,
0.709465
],
"translation": [
0, -3.15606e-007, -4.18033
]
},
"Bone_001": {
"rotation": [
0.00205211,
9.94789e-008,
0.000291371,
0.999998
],
"translation": [
0,
4.18717,
0
]
},
"Cylinder": {
"meshes": [
"Cylinder-mesh"
],
"skin": "skin_01",
"bounds": {
"jointGeometryMins": "mins_accessor",
"jointGeometryMaxs": "maxs_accessor"
}
}
}
}
|
It seems "inverseBindMatrices", "jointGeometryMins" and "jointGeometryMaxs" are inherently tied to a specific mesh (you can't use them with a different mesh anyway). So what about leaving those on the mesh (and get rid of "bindShapeMatrix") and having a skin that just lists an array of joints allowing a single mesh to be mapped to multiple sub-skeletons. Does COLLADA support multiple meshes per node? |
Yes, but we don't have to do the same in glTF, imho.
+1 for tying them with
Can we re-use joint-tree with different meshes? Otherwise there's no need in a Also we should state all reqs on the elements of
|
@lexaknyazev is it reasonable to scope these skinning changes for @mre4ce it is perfectly valid for you to put |
Absolutely reasonable, current skinning indirection isn't perfect. |
Sounds good, I defer to you and @mre4ce here - our skinning experts! |
CC #624, "Tightening up skinning" |
@pjcozzi, you mention ".extras". Why is there a ".extras" field when using JSON you can just add arbitrary names directly on the object? While using ".extras" may avoid name conflicts with existing members, it doesn't avoid name conflicts between different extensions. |
|
@lexaknyazev is right; the main intention was to forward compatibility. Perhaps somewhat overkill, I think it is a safe practice that doesn't overly bloat the JSON and perhaps even nicely adds to the separation of concerns. |
@lexaknyazev changes for 1.1 for this issue are up to you and @mre4ce. I doubt I will provide much value here. |
Example in the comment above implies following schema changes from glTF 1.0:
@amwatson @mre4ce Other possible approach to tighten skinning is to add restrictions from #624 (comment) and #624 (comment):
|
AFAIK this is resolved in glTF 2.0, with a few remaining issues that are being addressed elsewhere. |
I don't feel like the original question has been addressed at all. It seems to me that, currently, to do culling of animated nodes, the same process as mentioned in the original post has to be applied? |
You're right. The issue is more about culling rather than tightening the spec. |
For what it's worth, the accessor min/max for POSITION gives only a bounding box, not necessarily the tightest one (unless (Lazy way of computing min/max: append [-3.4028235e38, -3.4028235e38, -3.4028235e38, +3.4028235e38, +3.4028235e38, +3.4028235e38] to the end of your POSITION array and give It is a more minor point, but for a mesh with multiple primitives or with morph targets you'd need to do some computation to find out what the whole mesh's bounding box is. So even for a static mesh, there isn't really a good bounding box. For representing bounding volumes for a mesh instance, a really simple idea is to use a "standard volume" in the space of some node created for that purpose, eg. the node that instantiates the mesh just has to say in an extension "the unit cube in the space of node X is my bounding box". Everything else would be normal glTF: for an AABB you just set the translation on X to min and the scale to max-min; to animate the bounding box you just animate these properties like normal. |
Closing as duplicate with #507 (comment). Proposals are very welcome. Feel free to re-open if you have a proposal. |
While the accessor min/max for POSITION can be used to cull a static mesh, there appears to be no good way to cull an animated mesh. To cull animated meshes I ended up adding:
These are accessors to arrays of GL_FLOAT VEC3 with for each joint the local space mins/maxs of all vertices that are influenced by the joint. Using affine joint transforms, it is not particularly computationally intensive to calculate the global space mins/maxs for each joint.
Accumulating the mins/maxs for all joints of a skin is trivial.
I chose AABBs as bounding volumes because of their simplicity.
You could use spheres but you would still have to offset them from the joints to accurately contain the geometry (imagine a car driving around attached to a single joint that is not centered inside the car). It is also unclear what to do with a sphere that is attached to a joint with non-uniform scaling.
You could use OBBs as bounding volumes but in my experience OBBs tend to add complexity and a lot of extra math for small culling gains. Non-uniform scaling of OBBs is also non-trivial.
At the end of the day culling is a trade between CPU and GPU usage. Spending a lot of time on the CPU is usually ill-advised.
The text was updated successfully, but these errors were encountered: