-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
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
Allow >4 bones to skin a vertex #26137
Comments
Well, that's not quite true. |
Would be great if you can submit a PR with the changes and include an example with a model that uses more than 4 bones. Then we'll be able to measure performance and consider the maintenance costs. |
@RemusMar Thanks for elaborating on how the loaders pick which bones to remove. I'm not that familiar with the loader code yet so I wasn't fully aware of how it decided to remove them. I agree that 4 bones per vertex is usually enough and more is a special case. I'm also aware that this will be less performant, but to @mrdoob's comment, we don't know how much of a difference this is yet. Unity indicates support for it but I haven't tried it. I want to make this a toggle in the shader so that the current shader setup is the default. If someone desires more than 4 bones per vertex then it would switch the shader implementation. This way, the performance difference is only seen when the feature is enabled. I'll comment here once I having a working PR for this. |
Hi Remus, That's good to know that authoring tools should have controls to limit the bone influence but unfortunately there are still some cases where the company I work at wants to use models with >4 bones per vertex. I've advocated for keeping things under 4 and this will be the majority of models, but there are still some cases where they want higher. For some context, the company was previously using a custom WebGL-based renderer with the same shader approach to >4 bone influences as I've described. That renderer is being abandoned in favor of using Unity (not running on web). In some environments, Unity is undesirable so a 2nd rendering system is needed. Three.js fits well but the one feature missing is the ability to handle a larger number of bone weights. |
I found out that Babylon.js has support for >4 bone influences when I tried previewing a In the animations below, notice the jagged artifacts above the upper lip in Three.js that are not present in Babylon.js. This model was exported with max 16 bone influences although I don't know what the real max number was. When I use a copy of the mesh that was exported with max 4 bone influences, the animation has the same artifacts regardless of the renderer.
Babylon's docs don't make this clear, but it seems to do this by using an extra vertex buffer for weights and indexes that holds 4 additional influences Babylon's docs for their mesh class don't mention a max number for mesh bone influences other than it defaults to 4. There's also a seemingly-outdated doc about not supporting more than 4. |
Cory, babylon.js supports up to 4 bones influences per vertex. In your 1st GIF, the first 4 weights are normalized and in the 2nd one they are not. |
Hi Remus, Thanks for your response! I dug deeper into the Three.js's GLTFLoader class and the Babylon.js codebase to see what's going on. GLTFLoader is normalizing the first weights attribute buffer (the first 4 weights). Currently, only the glTF I inspected the ordering of weights in the glTF For example, here are the 16 weights for an individual vertex in my mesh across the 4 glTF buffers:
Notice that it is ordered by descending weight and if only the first 4 are used (and renormalized to 100%) then 30% of the original influence is gone. This is what creates the artifacts. Since this mesh's weights are ordered from highest to lowest, The code that I've seen in Babylon.js (1, 2, 3) indicates they are optionally using 1 extra vertex buffer to support up to 8 weights instead of 4. This explains why the animation artifacts are not easily visible when it loads my model exported for 16 weights. For the example vertex above, it would only lose 14% of the original influence instead of 30%. Most of the vertices don't have this much of a difference so the final artifacts in Babylon.js for this mesh/animation are minor. When I have Babylon.js load a model that I've exported from Blender which limits the weights to 4 (a single buffer), it has the same artifacts as Three.js. Again, this shows that the artifacts are due to missing bone weights and not a lack of normalization. Finally, I've implemented the skinning-weight-texture approach that I started this issue with and the artifacts completely disappear. See the new animations below:
I am currently working on an example which shows the difference in behavior and cleaning up the code for review. Regards, |
Cory, |
Hi Remus, I don't have much experience with creating rigs to know if the model could be skinned differently with less than four bones while still achieving the same quality of animation. This model was given to me by an artist as a sharable example to show the kind of artifacts that come up when bones are limited. The impression I got from them was that the 4 bone limit is always challenging and limits the expressiveness of the models they create. They frequently encounter this limit when creating highly expressive models. This can be "fixed" for models by spending more artist time on rearranging and re-weighting bones, but some models will still have a trade off between expressiveness or < 4 bone influences. The choice depends on the use-case. For example, video games might want more expressiveness during cutscenes but otherwise limit to 4 bones during gameplay that has hundreds of models on screen. A V-tuber tracked-avatar system that renders a single model would want higher expressiveness. Regards, |
Related issue: mrdoob#26137 (mrdoob#26137) This PR adds an alternative skinning approach to the existing bone index/weight vertex buffers which uses a vertex shader texture to support an arbitrary amount of bone influences per vertex. The current vertex buffer skinning approach limits meshes to <= 4 bone influences per vertex. For example, a mesh with the following bone weights/indices in vertex buffers... ``` WEIGHTS_0 JOINTS_0 --------------- -------- [index] 0 1 2 3 0 1 2 3 v0: 0 1.0 0 0 0 1 2 3 v1: 0.1 0.7 0.1 0.1 0 1 2 3 ``` becomes this array/texture of (index, weight) pairs: ``` v0 v1 -------- ----------------------------------- [index] | 0 | 1 | 2 3 4 5 | 6 data: (1, 1.0) (-1, -1) (1, 0.7) (0, 0.1) (2, 0.1) (3, 0.1) (-1, -1) ⬆ ⬆ ⬆ sentinel value bone index weight ``` and this vertex buffer: ``` weights texture start index --------------------------- v0: 0 v1: 2 ``` *I have worked on this change as part of my job at Google since my org has projects which would like Three.js to have this feature.* **What changed:** 1. If a model has more than 1 bone weight buffer (>4 weights per vertex) then a bone weight texture is created and the shader behavior is changed to use it instead of vertex buffer skinning. 2. Vertex weight/index buffers are sorted by weight -- across all buffers -- before normalization in the buffers that will be used. - This allows skin texture creation to only include non-zero weights. - This "fixes" some artifacts for vertex buffer skinning when models have >4 weights and the higher weights are not in the first 4 weights. - This also means that loading skinned meshes takes longer because the per-vertex weights are sorted in addition to being normalized. **What did not change:** 1. The vertex buffer skinning approach is still the default for models that have at most 4 bone weights per vertex. **What needs decisions:** 1. Default behavior and how to control which skinning method to use - Currently defaults to the old buffer method if a model has <= 4 weights otherwise the texture method is used. - Loader/mesh options have controls for **always** or **never** using the texture approach. 3. Do we still want to keep the old method? - The code would be simpler without it and I haven't seen a big performance impact. - Main tradeoff seems to be model load time (creating the skinning texture from the vertex buffers) - I'm unsure if other parts of three.js or clients require/assume the presence of the skinning buffer **What is still being worked on** 1. Tests 2. Documentation 4. Adding support to loaders of file formats other than glTF. **Examples** 1. webgl_animation_skinning_many_bone_influences.html - Shows the difference between < 4 and >= 4 bone skinning for a model that was created with 16 bone skinning. - Notice: - The artifacts on the upper lip (left image, only 4 weights) - The difference in how much the nose gets pulled up/down with the mouth movement. ![frontal-close-up-4-vs-16-bones](https://github.com/mrdoob/three.js/assets/3453535/249a5233-97bc-4576-bdd9-fed9d071fb40) 5. webgl_animation_skinning_performance.html - loads many skinned meshes playing animations with a toggle between the old and new behavior to see if there are performance differences. ![perf-test](https://github.com/mrdoob/three.js/assets/3453535/7173d049-b846-4643-b297-47bbf1a1e9c3) **Performance** All performance numbers are from a 2019 MacBook Pro running Chrome 114.0.5735.106. **Framerate** `webgl_animation_skinning_performance.html` renders at the same 23 fps for both the vertex buffer and vertex texture skinning methods. The Chrome profiling image below shows that GPU code is executing for about 25% of the frame (11ms / 45ms) and vertex skinning is only a portion of that so this benchmark scene doesn't do the best job of stressing the vertex skinning: <img width="2231" alt="perf-test-profile" src="https://github.com/mrdoob/three.js/assets/3453535/fe629821-e8f8-42fb-adc8-7de5ab4ea00a"> That being said, the soldier model still seems like a realistic asset that someone would use which is why I used it in the benchmarking scene. If there are other animated models with higher vertex counts that would increase GPU vertex shader runtime differences, I am happy to try them. **Note:** Texture skinning could have beneficial performance for models with <= 4 weights per vertex because it strips out weights that are zero. For the `Soldier.glb` model, this removed 38% of the weights. **Model Loading** method | `Soldier.glb` (7434 vertices, 4 weights) runtime| `HeadWithMax16Joints.glb` (2474 vertices, 16 weights) runtime --------|---------------------------|--------------- `SkinnedMesh.normalizeSkinWeights (dev)` | 5 ms | N/A `SkinnedMesh.normalizeSkinWeights (this PR)` | 12 ms | 21 ms `SkinnedMesh.createBoneIndexWeightsTexture` | 7 ms | 8 ms `GLTFLoader.load` (buffer skinning) | 190-380 ms | N/A `GLTFLoader.load` (texture skinning) | 190-380 ms | 240-290 ms The new implementation of `SkinnedMesh.normalizeSkinWeights()` is slower because it sorts weights in addition to normalizing them. It could be made faster by sorting each vertex's buffer data in-place instead of copying to separate arrays and then copying them back. The total load time and variance of the load time was so large that the additional processing in `normalizeSkinWeights` and `createBoneIndexWeightsTexture` did not have a noticeable effect.
I've created a draft PR for this now: #26222 |
PR is no longer a draft. I am now waiting on reviews. |
Description
Currently, ThreeJS limits how many bones can skin a vertex to at most 4. When a loader sees more than 4 it arbitrarily removes some of them and emits a warning. This results in broken rigs.
Some file formats and mesh/rig authoring tools allow vertices to be skinned by more than 4 bones. I have some meshes with this need so I would like to add support to ThreeJS for more than 4 bone weights per vertex.
I have started modifying ThreeJS to add this support and was wondering if it would likely be accepted?
How bone skinning currently works
Vertex shaders receive per-vertex skinning weights + indexes:
skinIndex
is used to retrieve the bone matrices and thenskinWeight
weights them to get the final vertex.The
skinIndex
andskinWeight
vertex buffers are directly created by loaders at load time and attached toBufferGeometry
.How I propose supporting >4 weights per vertex
Instead of sending bone indexes and weights to the vertex shader as vertex buffer data, it would be uploaded as a data texture and a single int per-vertex for the starting point in the bone-index-weight-pair texture.
The shading code would read from the bone pair texture until it saw an index of -1 or the maximum number of vertices,
MAX_BONES_PER_VERT
, is reached:Instead of loading skinning index/weight data directly to a vertex buffer on
BufferGeometry
, loaders should create a data texture if a flag tells it to. It could also detect if any vertex has more than 4 bone weights and make this decision dynamically but it's easier to keep it as a configuration flag. A new texture onSkinnedMesh
seems like the right place for this to live.This change in the shaders would be controlled by a new
#define
so that the existing behavior is maintained when the extra bones aren't needed.Alternatives
More shader attributes can be added to support additional bone weights but there's a limit on the amount of vertex data.
MAX_VERTEX_ATTRIBS
for WebGL is 16. The more attributes are used, the less likely a browser is to support the shader. Putting this data in a texture doesn't run into a similar limit although it might make performance worse.Additional context
Unity supports unlimited blend weights as a parameter: https://docs.unity3d.com/2019.2/Documentation/Manual/class-QualitySettings.html#BlendWeights
Someone previously had an example of FBX deleting weights when there were more than 4 per vertex: https://discourse.threejs.org/t/is-the-limit-of-4-skinning-weights-per-vertex-a-hard-limit-of-webgl/801
Their related Github issue: #12127
The text was updated successfully, but these errors were encountered: