# SDEF Skinning SDEF (Spherical Deform) is PMX's fourth weight type, alongside BDEF1, BDEF2 and BDEF4. Where BDEF blends bones linearly, SDEF blends them *spherically* around a per-vertex centre so that skin bends around a joint instead of collapsing into it. It is what keeps shoulders, elbows, wrists and the base of the neck from pinching when a limb rotates hard. MMD applies it to a small but visible fraction of a model's vertices, and getting it right is the difference between a clean elbow and a candy-wrapper crease. UMT reproduces SDEF exactly, and it does so through a full-mesh skinning pass that runs the spherical blend every frame against the live bone poses. That pass has **two implementations that compute the same result**: a GPU compute shader and a CPU Burst job. Which one runs is chosen per model by `MMDTransformManager.sdefSkinningMode` (`GPU` by default), and a missing compute shader forces the CPU path. There is no CPU-vs-GPU decision based on hardware capability — the mode is a setting, not a probe — so the "platform requirement" story on the old version of this page no longer applies. ## What SDEF stores Each SDEF vertex carries, in addition to its two bone indices `b0`, `b1` and the first bone weight `w0` (with `w1 = 1 − w0`): - **C** : the skinning *centre*, the pivot the vertex rotates around. - **R0**, **R1** : two reference points that bias the centre toward each bone. `PMXReader` converts all three to Unity space as it parses (the 180° Y rotation and the 0.08 unit scale, exactly as for any other position, see [MMD Formats and Internals](MMD-Formats-and-Internals.md#coordinate-conversion)). By the time the mesh builder sees them they are already in Unity units, and nothing scales them again. ## The maths The formula has two halves that are averaged: a *translation* estimate that moves the centre linearly with the bones, and a *rotation* estimate that swings the vertex around that centre with a slerped bone rotation. Splitting the vertex's motion this way is what produces the rounded, volume-preserving bend. ### Import-time correction The raw `R0`/`R1` do not blend back to the centre on their own, so UMT precomputes a corrected pair once at import (`MMDSDEFMath.CorrectSDEF`, called from `PMXMeshBuilder`). With `w1 = 1 − w0`: ```text Rblend = R0·w0 + R1·w1 R0' = C + R0 − Rblend R1' = C + R1 − Rblend ``` The correction guarantees that `R0'·w0 + R1'·w1 = C`, i.e. under the rest pose the reference points collapse exactly onto the centre, so an un-posed SDEF vertex sits precisely where it started. The corrected `R0'`, `R1'` and the centre `C` are baked into the per-vertex `SDEFVertexData` uploaded to the skinner; the raw `R0`/`R1` are never sent. ### Per-frame deform Let `M0`, `M1` be the two bones' skinning matrices for this frame (each is `boneWorldMatrix · bindpose`, rebased into the renderer's root-bone space) and `q0`, `q1` their rotation quaternions. For a bind-pose vertex `p` with normal `n`: **Translation half** : move the centre linearly with each bone, then blend: ```text p0 = M0 · C (C transformed by bone 0) p1 = M1 · C (C transformed by bone 1) t = (p0 − C)·w0 + (p1 − C)·w1 (blended displacement of the centre) Ptrans = C + t ``` **Rotation half** : transform the corrected reference points and blend: ```text Prot = (M0 · R0')·w0 + (M1 · R1')·w1 ``` **Averaged centre** : the SDEF pivot for this frame: ```text Pc = (Ptrans + Prot) · 0.5 ``` **Spherical bone rotation** : slerp the two bone rotations by the *second* weight, so `w1 = 0` gives all of bone 0 and `w1 = 1` gives all of bone 1: ```text q = slerp(q0, q1, w1) ``` **Final vertex** : rotate the vertex about the original centre `C`, then place it at the averaged pivot `Pc`: ```text p_out = q · (p − C) + Pc n_out = q · n ``` This is the standard MMD/Saba SDEF form: rotation is taken about the vertex's own centre `C`, while the pivot it lands on, `Pc`, is the average of the linear-translation and rotation estimates above. The slerp uses a shortest-path sign flip and an nlerp fallback when `q0` and `q1` are nearly parallel, to avoid dividing by a near-zero `sin θ`. Contrast this with the BDEF (linear blend) path the same pass runs for every non-SDEF vertex, which is the familiar weighted sum of transformed positions: ```text p_out = Σ wi · (Mi · p) n_out = Σ wi · (qi · n) ``` The linear form moves each vertex straight toward the bone; the SDEF form curves it around the joint. That curvature is the whole point. The two forms live side by side in one pass: a per-vertex deform-type flag (`SDEFVertexData.sdefC.w`, 0 = linear, 1 = SDEF) selects which is applied, so a mesh that mixes SDEF and BDEF vertices is handled correctly. ## How it runs in the pipeline SDEF is a full-mesh skinning pass, not a CPU pre-bake of a static pose, because the spherical blend has to be re-evaluated every frame against the live bone poses. The same pass also folds in vertex-morph blend shapes, in bind-pose space before skinning, so morphs and SDEF coexist correctly. - **Import.** `PMXMeshBuilder` detects any SDEF vertex in a generated mesh, marks the mesh `hasSDEF`, and fills a per-vertex `SDEFVertexData` buffer (corrected `R0'`/`R1'`, centre, bone indices and weights, and the deform-type flag). The mesh's vertex buffer is flagged for raw GPU access so the compute pass can read it. - **Build.** `MMDSDEFSkinnerBuilder` attaches one `MMDSDEFSkinner` component to each SDEF renderer, wiring the per-vertex data, the mesh bindposes, and a compact per-mesh morph table. It also sets `updateWhenOffscreen` on the renderer, because a mesh whose vertices are rewritten by the skinner can't compute its own bounds and would otherwise be culled wrongly. - **Runtime.** The owning `MMDTransformManager` drives the skinners once per frame, after the bone solve. The two modes are driven from different points in the frame: - **GPU mode** dispatches from the render-pipeline pre-render callback (both the SRP `RenderPipelineManager.beginCameraRendering` event and the built-in `Camera.onPreRender` are subscribed; only the active pipeline fires, and a frame guard keeps it to one dispatch per frame). The compute shader reads the bind-pose source buffer, applies morphs, applies skinning, and writes the result straight into the `SkinnedMeshRenderer`'s **output** vertex buffer, overwriting Unity's linear-blended result. Dispatching at camera-render time is what lets the compute overwrite win over Unity's own skinning. - **CPU mode** schedules the equivalent `MMDSDEFSkinJob` (a Burst `IJobParallelFor`) from `LateUpdate`, completes it, and uploads the result into a runtime pass-through mesh on the renderer. **CPU mode runs in play mode only.** Either way the bone matrices are sourced from the live `SkinnedMeshRenderer.bones`, so SDEF tracks whatever poses the skeleton — the MMD solver, an `Animator`, or manual posing alike. ## Choosing and configuring the mode The mode lives on the transform manager as `sdefSkinningMode` (`SDEFSkinningMode.GPU` or `.CPU`), with `doSDEFSkinning` as a master on/off, and `sdefComputeShader` holding the compute kernel for the GPU path. The rules are simple: - **GPU is the default.** The compute shader ships on the `UMTResources` asset and is wired onto the transform manager at import. - **A null compute shader forces CPU.** If the resources asset is stripped or misconfigured and no SDEF compute shader is present, import raises the warning *"SDEF meshes were imported but no SDEF compute shader is assigned on UMTResources; falling back to CPU SDEF skinning."* and sets the model's mode to CPU. - **You can set the mode yourself** on the `MMDTransformManager` if you want to force one path — for example CPU on a target where you don't want to rely on the compute overwrite path, keeping in mind CPU SDEF is play-mode only. There is no hardware-capability check anywhere in the package: the mode is exactly what `sdefSkinningMode` says (adjusted only by the null-shader fallback above). Both paths produce the same deform; the choice is about *where* the work runs, not whether SDEF is faithful. ## Debugging notes - If a model you know uses SDEF shows pinched joints, first confirm the SDEF pass is actually running: check that `doSDEFSkinning` is on, that the renderer has an `MMDSDEFSkinner`, and — for GPU mode — that `sdefComputeShader` is assigned. If import warned about a missing compute shader, the model is on the CPU path, which only runs in play mode, so an edit-mode preview will look un-deformed. - Any vertex that never resolves to SDEF (its source weights weren't SDEF) simply runs the linear path in the same pass; that is expected for the BDEF portion of a mixed mesh. - `SDEFVertexData`'s layout is byte-for-byte shared with the `struct` in `MMDSDEFSkinning.compute` (every field is 16 bytes), and the CPU job reads the same struct. Changing one side without the others corrupts the buffer, the same discipline the physics marshalling structs need. See [MMD Formats and Internals](MMD-Formats-and-Internals.md#skinning) for where SDEF sits among the other weight types, and the [Programming Reference](Programming-Reference.md) for the surrounding import API.