feat(unity-demo): OpenVAT demo for Unity — Built-in Render Pipeline#672
Conversation
Adds tools/unity-vat-demo/, the Unity counterpart to the existing Godot and Unreal sample projects. Same Rumba bake, same shader contract — a stock-Unity reference port for integrating an OpenVAT bake into a Unity 2022.3 LTS project (or newer) without URP/HDRP/Shader Graph. Ships: - C# scripts: VATPlayer (binds shader + drives _CurrentFrame), OrbitCamera (mouse-drag orbit + wheel zoom), FPSOverlay (avg + min(1s) rolling readout), PerfSpawnerVAT / PerfSpawnerSkeleton (the 1000- instance apples-to-apples comparison), and an editor-only BootstrapVAT that auto-wires the bake fields when a VATPlayer is dropped onto a GameObject (parses the sidecar JSON). - Shader: tools/vat-shaders/openvat.shader (the file we already publish as the Unity reference) copied to Assets/Shaders/. - Bake: same Rumba artifacts as godot-vat-demo/assets/Rumba/ — source glTF/bin, diffuse PNG, packed positions+normals PNG, sidecar JSON. - Unity project skeleton: ProjectSettings/ProjectVersion.txt pinned to 2022.3.20f1 LTS, Packages/manifest.json with the minimum BiRP module set, and a .gitignore so a developer's editor-generated Library/Temp/ folders don't leak back into the repo. Why no pre-built .unity scenes: Unity scenes reference assets by GUID from .meta files generated on the developer's first import. Hand-rolled scenes break the moment another machine reimports. The README walks through a 60-second scene-assembly flow that produces the same scenes we'd ship. Wires the new demo into the docs page (website/src/DocsApp.jsx) — the `qtmesh vat` section now links Godot/Unity/Unreal demos side by side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a full Unity OpenVAT demo: runtime VAT player, editor autowire and import tooling, custom glTF importer preserving vertex order, performance spawners and scenes, editor build/CLI tools, project metadata and README, plus CI test timeout improvements. ChangesUnity VAT Demo Implementation
Website Documentation Update
CI/CD Test Timeout Improvements
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69968e99e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "dependencies": { | ||
| "com.unity.collab-proxy": "2.0.5", | ||
| "com.unity.ide.rider": "3.0.24", |
There was a problem hiding this comment.
Add glTF importer dependency for Unity 2022 demo
The demo assumes Assets/VAT/Rumba/source.gltf is imported as a usable prefab/mesh (used by BootstrapVAT and the perf scene setup), but this project manifest does not include a glTF importer package. On a clean Unity 2022.3 setup, that leaves source.gltf unavailable as a GameObject/mesh and breaks auto-wiring and scene setup until users manually install the importer. Add com.unity.cloud.gltfast (as already done in tools/unity-vat-test/Packages/manifest.json) so the checked-in demo works out of the box.
Useful? React with 👍 / 👎.
| } | ||
| _material = new Material(shader) { name = "VAT_" + name }; | ||
| _material.SetTexture("_PosTex", positionTexture); | ||
| if (diffuseTexture != null) _material.SetTexture("_MainTex", diffuseTexture); |
There was a problem hiding this comment.
Stop binding nonexistent _MainTex on VAT material
VATPlayer sets diffuseTexture into _MainTex, but this shader has no _MainTex property and the fragment path only uses _BaseColor. Because BootstrapVAT auto-populates diffuseTexture, this mismatch is hit by default: texture binding is ignored (and can emit missing-property warnings per instance), so the advertised diffuse texture path does not actually render.
Useful? React with 👍 / 👎.
Two follow-up fixes for the Unity demo: 1. `BootstrapVAT.cs`: the auto-wirer was reaching for the imported mesh via `GetComponentInChildren<SkinnedMeshRenderer>().sharedMesh`, which misses when Unity's glTF importer strips the rig to a static MeshFilter — common in Unity 6's default Model import settings. Enumerate the .gltf's sub-assets directly via `AssetDatabase.LoadAllAssetsAtPath()` and pick the first `Mesh` we find. Works regardless of whether the importer kept a SkinnedMeshRenderer or collapsed it. 2. README: add a "Mesh picker hint" so users who hit the same dead-end in the Source Mesh slot picker know about the `t:Mesh` filter and the expand-glTF-in-Project-view trick. Unity only lists top-level Mesh assets in the picker by default, but the .gltf's Mesh is nested inside the importer as a sub-asset. Also accepts the linter-bumped Unity 6 ProjectVersion / Packages manifest from the previous commit (no functional change — just the versions a 6000.x editor writes back when first opening the project). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
tools/unity-vat-demo/Assets/Scripts/FPSOverlay.cs (1)
34-44: ⚡ Quick winCache GUI styles; avoid per-
OnGUIallocations in a perf overlay.Recreating
GUIStyleeveryOnGUI()adds avoidable GC/CPU overhead and can contaminate the FPS numbers this script reports.Proposed refactor
public class FPSOverlay : MonoBehaviour { @@ float _smoothedAvg; float _windowMin = float.MaxValue; + GUIStyle _style; + GUIStyle _shadow; + + void Awake() + { + RebuildStyles(); + } + + void OnValidate() + { + if (Application.isPlaying) RebuildStyles(); + } + + void RebuildStyles() + { + _style = new GUIStyle(GUI.skin.label) + { + fontSize = fontSize, + fontStyle = FontStyle.Bold, + normal = { textColor = textColor }, + }; + _shadow = new GUIStyle(_style) { normal = { textColor = Color.black } }; + } @@ void OnGUI() { - var style = new GUIStyle(GUI.skin.label) - { - fontSize = fontSize, - fontStyle = FontStyle.Bold, - normal = { textColor = textColor }, - }; - // Drop-shadow for legibility against a busy scene. - var shadow = new GUIStyle(style) { normal = { textColor = Color.black } }; + if (_style == null || _shadow == null) RebuildStyles(); @@ - GUI.Label(new Rect(20 + 2, 14 + 2, 600, 60), text, shadow); - GUI.Label(new Rect(20, 14, 600, 60), text, style); + GUI.Label(new Rect(20 + 2, 14 + 2, 600, 60), text, _shadow); + GUI.Label(new Rect(20, 14, 600, 60), text, _style); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/unity-vat-demo/Assets/Scripts/FPSOverlay.cs` around lines 34 - 44, OnGUI is allocating new GUIStyle instances each frame (style and shadow) which hurts performance and skews FPS; change FPSOverlay to cache these styles as private fields (e.g., cachedStyle, cachedShadow) and create/update them in Awake/OnEnable or when fontSize/textColor change instead of inside OnGUI, reusing the cachedStyle and cachedShadow in OnGUI; ensure you copy the necessary properties (fontSize, fontStyle, normal.textColor) when updating so the overlay reflects runtime changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tools/unity-vat-demo/Assets/Scripts/Editor/BootstrapVAT.cs`:
- Around line 103-106: TryParseSidecar currently returns true when Frames parses
even if ParseVec3Field returned default (0,0,0) for missing/invalid Min/Max;
update the function so it only succeeds when Frames > 0 AND both Min and Max
were actually parsed. Concretely, change the ParseVec3Field calls (or add new
TryParseVec3Field) so you can detect success for mn and mx, or check the JSON
for the "Min"/"Max" keys before calling ParseVec3Field; then return (frames > 0
&& mnParsed && mxParsed). This ensures ParseIntField(frames) and
ParseVec3Field(min) / ParseVec3Field(max) all signal success before
TryParseSidecar returns true.
In `@tools/unity-vat-demo/Assets/Scripts/FPSOverlay.cs`:
- Line 12: The public int windowFrames in FPSOverlay can be negative from the
Inspector which leads to dequeuing past zero; fix by clamping it to a
non-negative value (preferably >=0 or >=1 as you need) and guard the dequeuing
logic. Add an OnValidate (or Awake) that sets windowFrames = Mathf.Max(0,
windowFrames) and change the dequeuing loop in the Update/Render method to while
(frameTimes.Count > windowFrames) { ... } (or use Mathf.Min/Max checks) so the
queue is never dequeued when empty; reference FPSOverlay, windowFrames,
OnValidate/Awake, and the Update dequeuing loop to locate the changes.
In `@tools/unity-vat-demo/Assets/Scripts/PerfSpawnerVAT.cs`:
- Around line 94-95: In PerfSpawnerVAT.Update(), guard phaseJitter before using
it in the modulo: ensure phaseJitter is positive (e.g., clamp to a minimum > 0
or fallback to 1) before computing float phase = (i * 7919) % phaseJitter; so
that when phaseJitter is zero or negative you either skip the modulo and set
phase = 0 or use a safe positive value, then call
_players[i].SetCurrentFrame(_clock + phase) as before; update the check near the
current computation of phase in Update() referencing phaseJitter, phase,
_players and SetCurrentFrame.
---
Nitpick comments:
In `@tools/unity-vat-demo/Assets/Scripts/FPSOverlay.cs`:
- Around line 34-44: OnGUI is allocating new GUIStyle instances each frame
(style and shadow) which hurts performance and skews FPS; change FPSOverlay to
cache these styles as private fields (e.g., cachedStyle, cachedShadow) and
create/update them in Awake/OnEnable or when fontSize/textColor change instead
of inside OnGUI, reusing the cachedStyle and cachedShadow in OnGUI; ensure you
copy the necessary properties (fontSize, fontStyle, normal.textColor) when
updating so the overlay reflects runtime changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5a648641-1200-41fb-82d1-40f813dd0e5b
⛔ Files ignored due to path filters (6)
tools/unity-vat-demo/Assets/Shaders/openvat.shaderis excluded by!**/*.shadertools/unity-vat-demo/Assets/VAT/Rumba/Boss_diffuse.pngis excluded by!**/*.pngtools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_ogre_bind.binis excluded by!**/*.bintools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_pos.pngis excluded by!**/*.pngtools/unity-vat-demo/Assets/VAT/Rumba/source.binis excluded by!**/*.bintools/unity-vat-demo/Assets/VAT/Rumba/source.gltfis excluded by!**/*.gltf
📒 Files selected for processing (12)
tools/unity-vat-demo/.gitignoretools/unity-vat-demo/Assets/Scripts/Editor/BootstrapVAT.cstools/unity-vat-demo/Assets/Scripts/FPSOverlay.cstools/unity-vat-demo/Assets/Scripts/OrbitCamera.cstools/unity-vat-demo/Assets/Scripts/PerfSpawnerSkeleton.cstools/unity-vat-demo/Assets/Scripts/PerfSpawnerVAT.cstools/unity-vat-demo/Assets/Scripts/VATPlayer.cstools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com-remap_info.jsontools/unity-vat-demo/Packages/manifest.jsontools/unity-vat-demo/ProjectSettings/ProjectVersion.txttools/unity-vat-demo/README.mdwebsite/src/DocsApp.jsx
…d glTF Unity 6 doesn't ship a built-in glTF importer; `.gltf` files land as plain data files (no Mesh sub-asset, no Model prefab), which is why the Source Mesh picker came up empty even with the `t:Mesh` filter. Convert the same mesh via `qtmesh convert source.gltf -o source.fbx` (through the same Ogre intermediate the baker walked → 5828 verts in the same order, position-texture columns still align). Keep the .gltf in the bake folder for parity with the Godot + Unreal demos. BootstrapVAT now probes `source.fbx` first and falls back to `source.gltf` (so the auto-wirer keeps working if a future user installs UnityGLTF and prefers the glTF), and the helper is renamed FindFirstMeshInGltf → FindFirstMeshInModel to reflect the broader role. README explains the FBX-vs-glTF choice in a callout next to the import-settings step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…the runner for 6h PR #672's first CI run hung 3 hours+ in BoneDragReleaseTest with the last log line on `[ RUN ] BoneDragReleaseTest.NoAnimSetsInitial` — no PASSED/FAILED follow-up. The suite was deadlocked (likely an Ogre / Mesa render-system shutdown that doesn't reliably return on Xvfb), but because the workflow inherited GitHub's default `timeout-minutes: 360` the job kept burning runner time until I manually cancelled. Two layers of timeout so this surfaces in minutes, not hours: 1. **Job-level `timeout-minutes: 45`**. Full sweep normally takes ~12 min (build cache restore + Xvfb + ~25 suites + coverage + sonar). 45 min is a generous ceiling — beyond that something is very wrong and we want the job to abort. 2. **Per-suite `timeout --foreground --signal=KILL 300`**. The slowest legitimate suite is ~90s (FBX import round-trip); 5 min covers anything reasonable. SIGKILL goes straight in because a deadlocked Qt event loop / GL driver doesn't honour SIGTERM consistently. `--foreground` keeps timeout usable inside the for-loop where the shell isn't a session leader. Exit code 124 from timeout(1) is treated as a non-fatal WARNING and counted in CRASHED_SUITES (same shape as the existing GL-crash allowlist). The remaining suites still run so we get partial coverage + a clear log naming the hung suite, instead of a silent multi-hour wedge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Mesh picker in the Source Mesh slot stays empty even with the FBX in the project because Unity hides sub-assets (the Model importer's nested Mesh) from the picker by default. The BootstrapVAT auto-wirer covers this — but it only fires on `componentWasAdded`, which races against the FBX importer on first project open. Add a custom Inspector for VATPlayer with an "Auto-Wire from Bake" button that calls into the now-public `BootstrapVAT.AutoWire(player, verbose:true)`. The button is a user-driven escape hatch that always works regardless of import timing — click it once the FBX has finished importing and the slot populates. Preserves any slots the user already filled in manually; always overwrites frameCount + bounds from the sidecar (single source of truth). Renames `TryAutoWire` → public `AutoWire` and threads a `verbose` flag so the auto-fire-on-add stays quiet during normal use while the button's invocations log to the console. README updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t settings The Source Mesh picker was empty because Unity's FBX importer was configured as Humanoid (animationType: 2) — humanoid rigs bury the Mesh inside the generated SkinnedMeshRenderer + Avatar bundle and don't expose a selectable standalone Mesh sub-asset in the picker. Fixes by enforcing three import settings on every reimport of files under Assets/VAT/Rumba/: ModelImporter (source.fbx): • animationType = None → exposes the Mesh as a top-level sub-asset • importAnimation = false → VAT texture replaces the Animator • isReadable = true → VATPlayer.EnsureUV2 writes uv2 at runtime • optimizeGameObjects = false → keeps the prefab walkable TextureImporter (mixamo.com_pos.png): • sRGBTexture = false (positions are linear data) • textureCompression = Uncompressed (lossy comp corrupts positions) • filterMode = Point (no bilinear blur between frame rows) • wrapMode = Clamp • mipmapEnabled = false (mips of packed data are nonsense) • alphaSource = None (bake has no alpha) Implemented as a `VATAssetPostprocessor : AssetPostprocessor` so the settings survive a Unity re-import — relying on hand-edited .meta is brittle since Unity rewrites parts on round-trip. The .meta change is the matching seed value for fresh clones. After this lands, on next open Unity will reimport source.fbx and the Source Mesh picker will list the freshly-exposed sub-asset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unity 6's FBX importer uses Autodesk's strict FBX SDK and rejects qtmesh's custom 7300 binary output with "File is corrupted: 'source.fbx'" (FBXImporter.cpp:422). The FBXExporter we ship works fine with Assimp + Blender + Maya, but Autodesk's own SDK is the strictest validator and trips on subtleties our writer doesn't get right. Rather than fix the FBX writer (a real, multi-day project), ship the mesh as Wavefront .obj — universally supported, no plugins needed, and same 5828 verts in the same order via qtmesh convert's Ogre intermediate. Confirmed: gltf and obj both report 5828 verts / 10220 tris. Changes: - Drop source.fbx + .meta from the repo. - Add source.obj + source.mtl (generated by `qtmesh convert source.gltf -o source.obj`). - Repo-root .gitignore had `*.obj` (for C/C++ objects); add exemption for `tools/*-vat-demo/Assets/**/*.obj` and matching `.mtl`. - BootstrapVAT probes source.obj first, falls back to source.gltf. - VATAssetPostprocessor enforces meshOptimizationFlags=0 + weldVertices=false. The bake indexes column N to the Nth rendered vertex; if Unity's optimizer reorders or merges verts, every column lookup misses and the playback turns to noise. - README explains the FBX-vs-OBJ-vs-glTF choice + drops the manual texture-import-tweak section (the postprocessor handles it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues visible in the first working playback of the Rumba dancer: 1. Only the head + torso rendered — arms, legs, eyes, cigar were missing. Root cause: VATPlayer assigned `sharedMaterial = _material` (singular). For a multi-submesh mesh like Rumba (11 submeshes — Skin_MAT / Clothes_MAT / Eyes_MAT / Cigar_Mat), Unity only draws submesh 0 unless `sharedMaterials` (plural) has one entry per submesh. Fix: detect `sourceMesh.subMeshCount` and replicate the same Material across all N slots. All submeshes share the same VAT texture + UV0 layout, so one material instance reused N times is correct. 2. Diffuse texture had no effect — the shipped shader only declared `_BaseColor`, no `_MainTex` sampler. The fragment shader did `_BaseColor.rgb * lighting` so Boss_diffuse.png was ignored. Fix: add `_MainTex` + `_MainTex_ST` properties, sample via `tex2D(_MainTex, uv * ST.xy + ST.zw)`, and gate the modulate with a `_UseDiffuseMap` toggle so users with a textureless bake can fall back to flat-tint mode. `_BaseColor` default flipped to white so the texture isn't pre-tinted when present. VATPlayer now binds `_MainTex` + flips `_UseDiffuseMap` accordingly. Sync the same shader changes into tools/vat-shaders/openvat.shader (the public template surfaced via `qtmesh vat --include-shaders unity`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unity wrote these on first import. Committing them keeps the asset GUIDs stable across machines — without them, every clone of the repo gets fresh GUIDs and any future scene/prefab that references the mesh breaks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rs reorder verts
Unity's OBJ importer rebuilds the vertex buffer in face-walk order
(rather than position-list order), and Unity's FBX importer rejects
qtmesh's 7300 binaries as corrupt. The Rumba dancer was rendering as
a recognisable but mangled blob of triangles — every vertex was
reading the WRONG column in the position texture because Unity's
import order didn't match the order the baker walked.
Two-part fix:
1. Re-bake with `qtmesh vat --emit-uv2` so the glTF's TEXCOORD_1
carries the per-vertex bake-column index. The column then "rides"
with each vertex through any importer reorder — this is the same
trick the Unreal demo uses.
2. Ship a 350-line `QtmGltfImporter.cs` ScriptedImporter that reads
the .gltf directly: positions, normals, UV0, UV1, indices. Each
glTF primitive becomes one Unity subMesh. The importer:
• does NOT call mesh.Optimize() / OptimizeIndexBuffers
• does NOT weld or dedupe verts
• does NOT touch the vertex array after writing it
so column-N in the position texture lines up exactly with the
Nth vertex Unity renders.
Includes a hand-rolled MiniJson parser (≈80 lines) so the demo
has no JSON.NET / Newtonsoft dependency.
Updates:
• Drop source.obj / source.mtl — no longer needed.
• Bake artifacts refreshed via `qtmesh vat --emit-uv2`. The new
glTF has TEXCOORD_1 (verified: 11 references in source.gltf).
• VATPlayer's EnsureUV2 keeps the synth-from-vertex-id fallback
but logs a warning when it fires — the right path is for the
importer to ship real UV2.
• VATPlayer.cs unchanged on the materials / shader binding from
the previous commit (multi-submesh + _MainTex modulation).
• README rewritten around the custom-importer story.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unity had cached `DefaultImporter` in the .gltf.meta from before the QtmGltfImporter ScriptedImporter was added — and Unity won't re-pick the importer for an already-classified asset just because a new ScriptedImporter shows up. The asset stayed a plain text file in the project view (text icon, no Mesh sub-asset), and the rendering blob the user was seeing was the leftover OBJ mesh asset from the prior attempt, still cached in Library/ even though source.obj/.mtl were deleted from the repo. Hand-write the .meta to point at QtmGltfImporter (matched by its script GUID 44513d2c47bc74161a0122cacaf529f0). On next editor open, Unity will see the asset is owned by our importer and re-import it, producing a real Mesh sub-asset that the VATPlayer can bind to. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…porter Two diagnostics in service of the still-mangled VAT replay: 1. Drop the X-negation + index-winding-flip from QtmGltfImporter. The shader replaces every vertex position via the texture sample, so what's on the source mesh's vertex array doesn't enter the rendering pipeline. Negating X / flipping winding was dead code that risked Unity recomputing bounds/tangents in ways that obscure the actual UV1 transport issue. 2. Add a Debug.Log on the importer that prints the imported UV1 range. If u=[0..5827], v=[0..0] the column indices survived; if something else, the bake's TEXCOORD_1 didn't survive the import. The user's latest screenshot shows the mesh wireframe is the correct T-pose silhouette but the rendered triangles are a fan from a single point — classic signature of every vertex sampling column 0. UV1 is either not being delivered or being clobbered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… column indices THE root cause of the fan-of-triangles rendering. VertexChannelCompressionMask in Unity's Player Settings defaulted to 4054, which has bit 4 (UV1) set — so Unity was compressing UV1 from Float32 to FP16 on GPU upload. FP16 has ~10 mantissa bits; integer values above ~1024 lose precision. The bake's UV1 holds per-vertex column indices in [0..5827]: prim[0]: u=[0..1023] ← head, renders correctly (FP16-safe) prim[1]: u=[1024..1437] ← FP16-quantized, snaps to wrong columns ... prim[10]: u=[4790..5827] ← FP16 loses ~3 bits, mass snap Hence the visible pattern: head + hair rendered correctly (those verts were in primitive 0, columns 0..1023), everything else fanned out from a single point in the bounds — those FP16-snapped indices landed on a small set of texture columns. Clear bits 3+4+5+6 in VertexChannelCompressionMask (UV0/1/2/3): before: 4054 (0b111111010110) after : 3974 (0b111110000110) Position/Normal/Color/Tangent/BlendWeight/BlendIndices stay compressed — they're not affected by this bake's data ranges. Also drops the dead X-negate / winding-flip block from the importer (the VAT shader overrides position entirely, so coord-system fixups on the source mesh are no-ops at best). Same diagnostic Debug.Log from the previous commit kept in place so users can verify the UV1 range in the Console. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tools/unity-vat-demo/Assets/Scripts/Editor/QtmGltfImporter.cs`:
- Around line 73-75: The code directly combines and reads uri into binPath
allowing path traversal; instead compute absolute paths and enforce containment:
get the canonical gltfDir (Path.GetFullPath(gltfDir)) and the canonical
candidate (Path.GetFullPath(Path.Combine(gltfDir, uri))), verify the candidate
either equals or starts with the canonical gltfDir plus a directory separator,
and only then call ctx.DependsOnSourceAsset and
buffers.Add(File.ReadAllBytes(...)); if the check fails, throw or skip with a
clear error. Ensure you reference the same variables (uri, gltfDir, binPath) and
update ctx.DependsOnSourceAsset to use the validated canonical path.
- Around line 244-277: ReadVec2/ReadVec3 assume tightly-packed float32 VEC2/VEC3
data and ignore accessor.componentType, accessor.type and bufferView.byteStride,
which corrupts interleaved or non-float accessors; update ReadVec2 and ReadVec3
to first validate that acc["componentType"] == 5126 (float32) and acc["type"] ==
"VEC2"/"VEC3" respectively and throw if not, then compute the needed byte length
per element using byteStride when present (fall back to tightly-packed 8/12
bytes when byteStride is 0 or missing) and call SliceAccessor (or extend
SliceAccessor if needed) with the correct total byte size/stride and count so
bytes are read per-element respecting stride rather than always assuming
contiguous float32 layout; reference the ReadVec2, ReadVec3 and SliceAccessor
symbols when making these changes.
In `@tools/unity-vat-demo/Assets/Scripts/Editor/VATAssetPostprocessor.cs`:
- Line 40: The current check uses assetPath.StartsWith(kBakeDir) which
incorrectly matches prefix-sibling folders; in VATAssetPostprocessor.cs, replace
that loose StartsWith check with a boundary-aware comparison: ensure assetPath
either exactly equals kBakeDir or begins with kBakeDir plus the path separator,
and perform the comparisons using StringComparison.Ordinal for determinism (i.e.
check equality OR StartsWith(kBakeDir + "/" , StringComparison.Ordinal)). This
tightens matching to the bake folder only.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e15424a2-5fed-4b67-9c64-f1f8bf11c5c8
⛔ Files ignored due to path filters (9)
tools/unity-vat-demo/Assets/Shaders/openvat.shaderis excluded by!**/*.shadertools/unity-vat-demo/Assets/VAT/Rumba/Boss_diffuse.pngis excluded by!**/*.pngtools/unity-vat-demo/Assets/VAT/Rumba/Boss_normal.pngis excluded by!**/*.pngtools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_ogre_bind.binis excluded by!**/*.bintools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_pos.pngis excluded by!**/*.pngtools/unity-vat-demo/Assets/VAT/Rumba/source.binis excluded by!**/*.bintools/unity-vat-demo/Assets/VAT/Rumba/source.gltfis excluded by!**/*.gltftools/unity-vat-demo/ProjectSettings/ProjectSettings.assetis excluded by!**/*.assettools/vat-shaders/openvat.shaderis excluded by!**/*.shader
📒 Files selected for processing (10)
.gitignoretools/unity-vat-demo/Assets/Scripts/Editor/BootstrapVAT.cstools/unity-vat-demo/Assets/Scripts/Editor/QtmGltfImporter.cstools/unity-vat-demo/Assets/Scripts/Editor/VATAssetPostprocessor.cstools/unity-vat-demo/Assets/Scripts/Editor/VATAssetPostprocessor.cs.metatools/unity-vat-demo/Assets/Scripts/VATPlayer.cstools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com-remap_info.jsontools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_pos.png.metatools/unity-vat-demo/Assets/VAT/Rumba/source.gltf.metatools/unity-vat-demo/README.md
✅ Files skipped from review due to trivial changes (5)
- .gitignore
- tools/unity-vat-demo/Assets/Scripts/Editor/VATAssetPostprocessor.cs.meta
- tools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com_pos.png.meta
- tools/unity-vat-demo/Assets/VAT/Rumba/mixamo.com-remap_info.json
- tools/unity-vat-demo/README.md
Adds the missing pieces so the demo can be built from a fresh clone
without opening the editor GUI:
- `CLIBuildScenes.BuildAll` — programmatically constructs Web.unity
and PerfVAT.unity using EditorSceneManager + the documented API.
Force-reimports source.gltf as part of the same pass so the
QtmGltfImporter's UV1 diagnostic surfaces in CI logs. Wires both
scenes into Build Settings.
- `CLIBuilder.{BuildMac,BuildWindows,BuildLinux}` — calls
BuildPipeline.BuildPlayer with the registered scenes. Writes to
tools/unity-vat-demo/Build/<platform>/.
- `ForceVATCompatibleSettings` — clamps `VertexChannelCompressionMask`
to 3974 (UV channels uncompressed) on editor load via reflection,
defending the setting against Unity's tendency to rewrite
ProjectSettings.asset back to defaults. Unity 6 made the public
setter `internal`, so we reach it via reflection; the .asset value
is the initial seed for fresh clones.
- `GraphicsSettings.asset` adds Hidden/QTM/VAT to
m_AlwaysIncludedShaders. Without this Unity strips it from
standalone builds since no .mat asset references it (VATPlayer
creates the Material at runtime via Shader.Find), and the
resulting binary logs "shader not found" forever.
- `.gitignore` exempts `/Build/` and Unity 6's auto-injected
Google Play / mobile-resolver scaffolding from being committed.
Verified end-to-end on macOS:
$ Unity ... -executeMethod CLIBuildScenes.BuildAll
→ built Assets/Scenes/Web.unity + PerfVAT.unity
→ QtmGltfImporter: imported 5828 verts across 11 submeshes
→ UV1 range u=[0..5827], v=[0..0]
$ Unity ... -executeMethod CLIBuilder.BuildMac
→ build SUCCEEDED — 112 MB, 0 errors
→ app launches without shader/script errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tools/unity-vat-demo/Assets/Scripts/Editor/CLIBuilder.cs`:
- Around line 44-51: The current CLIBuilder check for no scenes (the if block
that checks scenes.Count == 0) uses EditorApplication.Exit(0) which makes CI
succeed; change that to a non-zero exit to fail headless/batch runs — replace
EditorApplication.Exit(0) with EditorApplication.Exit(1) (or otherwise set a
non-zero Environment.ExitCode) in the CLIBuilder method where scenes are
validated so the process returns failure when no .unity scenes are found.
In `@tools/unity-vat-demo/Assets/Scripts/Editor/CLIBuildScenes.cs`:
- Around line 128-130: After assigning ps.sourceMesh = FindBakeMesh() and
loading textures via AssetDatabase.LoadAssetAtPath<Texture2D> for
ps.positionTexture and ps.diffuseTexture, validate each returned value and fail
fast if any are null: check FindBakeMesh(), ps.positionTexture and
ps.diffuseTexture, log a clear error naming the missing asset (mesh or specific
texture path), and abort the build/save flow (throw an exception or return
non-success from the method) so the scene is not saved as "built" with missing
VAT assets; modify the method containing these lines (e.g., CLIBuildScenes
method that sets ps.*) to perform the null checks and short-circuit the
remaining save/report logic when any required asset is missing.
- Around line 45-54: The current code overwrites EditorBuildSettings.scenes with
only webScene and perfScene, removing any existing PerfSkeleton.unity entry;
update the logic in the method that calls BuildWeb() and BuildPerfVAT() so it
reads the current EditorBuildSettings.scenes, ensures the boot scene (webScene)
is first, preserves any existing PerfSkeleton.unity entry if present, and then
adds/ensures perfScene is enabled — operate on the existing array/list of
EditorBuildSettingsScene objects (not replace it) using the webScene and
perfScene variables and the PerfSkeleton.unity filename to detect/preserve that
entry.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 20a2cf7c-eca4-4105-a354-3df778c5ef82
⛔ Files ignored due to path filters (7)
tools/unity-vat-demo/Assets/Scenes/PerfVAT.unityis excluded by!**/*.unitytools/unity-vat-demo/Assets/Scenes/Web.unityis excluded by!**/*.unitytools/unity-vat-demo/ProjectSettings/EditorBuildSettings.assetis excluded by!**/*.assettools/unity-vat-demo/ProjectSettings/GraphicsSettings.assetis excluded by!**/*.assettools/unity-vat-demo/ProjectSettings/PackageManagerSettings.assetis excluded by!**/*.assettools/unity-vat-demo/ProjectSettings/ProjectSettings.assetis excluded by!**/*.assettools/unity-vat-demo/ProjectSettings/UnityConnectSettings.assetis excluded by!**/*.asset
📒 Files selected for processing (14)
tools/unity-vat-demo/.gitignoretools/unity-vat-demo/Assets/Scenes/PerfVAT.unity.metatools/unity-vat-demo/Assets/Scenes/Web.unity.metatools/unity-vat-demo/Assets/Scripts/Editor/CLIBuildScenes.cstools/unity-vat-demo/Assets/Scripts/Editor/CLIBuildScenes.cs.metatools/unity-vat-demo/Assets/Scripts/Editor/CLIBuilder.cstools/unity-vat-demo/Assets/Scripts/Editor/CLIBuilder.cs.metatools/unity-vat-demo/Assets/Scripts/Editor/ForceVATCompatibleSettings.cstools/unity-vat-demo/Assets/Scripts/Editor/ForceVATCompatibleSettings.cs.metatools/unity-vat-demo/Assets/Scripts/Editor/QtmGltfImporter.cs.metatools/unity-vat-demo/Assets/VAT/Rumba/Boss_normal.png.metatools/unity-vat-demo/Packages/manifest.jsontools/unity-vat-demo/Packages/packages-lock.jsontools/unity-vat-demo/ProjectSettings/SceneTemplateSettings.json
✅ Files skipped from review due to trivial changes (9)
- tools/unity-vat-demo/Assets/Scripts/Editor/CLIBuildScenes.cs.meta
- tools/unity-vat-demo/Assets/Scenes/Web.unity.meta
- tools/unity-vat-demo/Assets/Scripts/Editor/ForceVATCompatibleSettings.cs.meta
- tools/unity-vat-demo/Assets/Scenes/PerfVAT.unity.meta
- tools/unity-vat-demo/Assets/Scripts/Editor/CLIBuilder.cs.meta
- tools/unity-vat-demo/ProjectSettings/SceneTemplateSettings.json
- tools/unity-vat-demo/Packages/manifest.json
- tools/unity-vat-demo/.gitignore
- tools/unity-vat-demo/Packages/packages-lock.json
…riangles cause
THE actual cause of the persistent rendering bug, found by a runtime
VATPlayer diagnostic that logs the position texture's effective width:
VATPlayer: runtime UV1 u-range on source = [0..5827]
(positionTexture width = 2048, expected max = 2047)
The Rumba bake's position texture is 5828 columns wide (one per
vertex), but Unity's TextureImporter caps it at 2048 by default
(both globally and per-platform). Every vertex whose UV1 column
index was > 2047 sampled the SAME boundary column (because we set
wrapMode=Clamp), giving every "lost" vertex the same position →
fan of triangles spraying out from a single point. This explains
why ~35% of the dancer (the head, columns 0-1023) looked recognisable
and the rest collapsed.
VATAssetPostprocessor now sets `maxTextureSize = 8192` on the
default settings AND on every per-platform override (Standalone /
WebGL / iPhone / Android) so the cap doesn't sneak back in on
build. Also sets `npotScale = None` so the 5828×142 texture doesn't
get rounded to 8192×256 (wasteful) or worse.
Build delta: 112 MB → 114 MB. The extra 2 MB is the position texture
stored at its native resolution.
Also drops the dead "VertexChannelCompressionMask" theory chase:
that wasn't the actual cause (UV1 was Float32 all along), it was
the texture downscaling. Kept the Float32 vertex layout
declaration in QtmGltfImporter as a belt-and-suspenders safeguard,
plus the FullPrecision toggle as a no-op-safe defensive measure.
Also adds the runtime UV1-range log to VATPlayer that surfaced this
bug — fires once per scene so the perf grid doesn't flood the log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…port The position texture is a 16-bit RGB PNG (per OpenVAT spec — gives ~30 µm precision over the bake's 2 m bounds). Unity's TextureImporter defaults to RGBA32 (8 bits/channel) regardless of the source PNG's depth, quantizing positions to 256 levels per axis. Over a 2 m bounding box that's ~8 mm of jitter per vertex per frame — every vertex jumps around inside an 8 mm cube, producing a cloud-of- confetti rendering with the right OVERALL silhouette but no internal structure. VATAssetPostprocessor now sets `settings.format = TextureImporterFormat.RGBA64` on every platform override (Standalone / WebGL / iPhone / Android). Verified at runtime: VATPlayer: positionTexture: 5828×142, format=R16G16B16A16_UNorm Build size: 114 MB → 118 MB (the +4 MB is the position texture now stored at 16 bpc instead of being downsampled to 8 bpc). Also adds `format` to the VATPlayer runtime diagnostic so this silent-quantization class of bug can be caught from the player log without needing the editor open. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flag The Rumba dancer still renders as garbled ellipsoids despite all of: - UV1 confirmed at u=[0..5827] runtime - positionTexture confirmed at 5828×142 R16G16B16A16_UNorm - Bounds + frameCount confirmed correct - Texture color samples in [0,1] as expected Adding a `_BypassVAT` toggle that short-circuits the vertex shader to output `UnityObjectToClipPos(IN.vertex)` instead of the texture-driven position. The Web scene boots with `bypassVAT = true` so the static T-pose mesh renders directly. What we're trying to learn: if bypass=true also gives ellipsoids, the mesh import is producing garbage vertex positions and the bug is in QtmGltfImporter. If bypass=true gives a recognisable T-pose, the mesh is correct and the bug is in the VAT replay path (shader or material binding). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After ~20 commits of incremental fixes on the custom BiRP shader path (UV channel compression, max texture size, RGBA64, vertex buffer layout, scripted importer, target SM 3.0+, tex2Dlod vs Load…), the Rumba dancer still renders as an egg-shaped blob under Unity 6 + Metal. The diagnostics confirm UV2 reaches the vertex shader per-vertex correctly and the texture is uploaded at full resolution in R16G16B16A16_UNorm — yet vertex-stage texture sampling returns constant values regardless of which API path we use. The author of the OpenVAT format ships a maintained Unity package at sharpen3d/openvat-unity that uses Shader Graph decoders and requires URP (or HDRP). That's the recommended path for now: tested, upstream, and orthogonal to whatever Unity 6 + BiRP + Metal corner case is biting us. Updates the top of the README with: - Step-by-step URP setup via the official package (URL, version requirements, the rename trick from `_pos.png` → `_vat.png` that OpenVATEditor's filename heuristic looks for). - A "Status" section explaining where the custom BiRP shader stands + what's been ruled out, so a future contributor can pick up the investigation knowing what's already been tested. Code under `Assets/Shaders/openvat.shader` + `VATPlayer.cs` etc. stays in place as a starting point for whoever wants to fix the BiRP path — it's not removed, just deprioritised relative to the package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eline
Replaces the custom BiRP `Hidden/QTM/VAT` shader path (which hit a
vertex-stage texture fetch wall under Unity 6 + Metal — egg-shaped
blob renders regardless of which sampling API we used) with the
upstream-tested path:
- URP 17.0.4 (com.unity.render-pipelines.universal) installed via
Packages/manifest.json.
- OpenVAT's official Unity package (com.lukestilson.openvat) pulled
from sharpen3d/openvat-unity, which ships Shader Graph decoders
+ an Editor tool that builds the material from a sidecar JSON.
- `Assets/OpenVATContent/` ingests the existing Rumba bake — same
source.gltf / source.bin + the position texture renamed to
rumba_vat.png (the suffix OpenVATEditor's filename heuristic
looks for).
- Color space flipped Gamma → Linear (URP requirement).
- `CLISetupURPAndBuild.cs` is a one-shot editor method that:
1. Forces Linear color space.
2. Creates a UniversalRenderPipelineAsset + UniversalRendererData
via reflection and binds them to GraphicsSettings + every
QualityLevel.
3. Invokes OpenVATEditor.ProcessOpenVATContent() (also via
reflection — the method is private) to consume the bake.
4. Builds a minimal scene around the generated prefab.
5. Runs BuildPipeline.BuildPlayer for StandaloneOSX.
Verified end-to-end:
$ Unity ... -executeMethod CLISetupURPAndBuild.Run
→ CLISetupURPAndBuild: set color space → Linear
→ CLISetupURPAndBuild: created URP asset at Assets/Settings/URP-Asset.asset
→ CLISetupURPAndBuild: wired URP asset into GraphicsSettings + every QualityLevel
→ CLISetupURPAndBuild: ran OpenVATEditor.ProcessOpenVATContent
→ CLISetupURPAndBuild: build SUCCEEDED (119320297 bytes)
The custom BiRP shader path lives on as an experimental code path —
not removed, but no longer the default. The README's "Recommended
path" section at the top now points at this URP setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r OpenVATEditor The OpenVATEditor.ProcessOpenVATContent path assigns `Renderer.sharedMaterial` (singular), which only covers submesh 0. For the multi-submesh Rumba dancer (11 slots — Skin / Clothes / Eyes / Cigar etc.) the other 10 stay null and Unity renders them with the missing-material pink — visible in the first URP build screenshot as a textured jacket surrounded by hot-pink arms / head / legs. CLISetupURPAndBuild now calls a `FixupMultiSubmeshPrefab` pass after OpenVATEditor finishes: walk the generated prefab, find every Renderer with a Mesh, and if `sharedMaterials.Length < subMeshCount` or any slot is null, replicate the *_mat.mat VAT material across every slot. All submeshes share the same VAT decoder + texture, so one material instance reused N times is the correct fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er OpenVATEditor
The first URP build of the dancer rendered in a textured T-pose but
never animated. Three reasons:
1. OpenVATEditor seeds `_speed = 0` on the material it creates
(Unity's `CreateInstance(Material)` zeros all floats; OpenVATEditor
doesn't touch _speed). The shader's `_Time.y` multiplier then has
nothing to scale by → frame index stays at 0.
2. OpenVATEditor reads the position texture's `.height` value as
`_resolutionY` for the frame-row math. But the texture had been
imported under Unity's default 2048 max-size + downscale-NPOT
pipeline, which collapsed the bake's 142 rows to 64. With
_resolutionY=64 the shader's row-lookup math overshoots, sampling
non-position rows.
3. VATAssetPostprocessor only watched `Assets/VAT/Rumba/` AND only
matched files ending in `_pos.png`. OpenVAT's official path uses
`Assets/OpenVATContent/` + `_vat.png`, so the postprocessor never
fired on the OpenVAT texture and let Unity's defaults strip the
resolution.
Fixes:
- VATAssetPostprocessor now watches both folders (kBakeDirs[]) and
matches both `_pos.png` and `_vat.png` filename suffixes.
- CLISetupURPAndBuild force-reimports the *_vat.png BEFORE running
OpenVATEditor, so the postprocessor's maxTextureSize=8192 + RGBA64
settings apply before the material is created and reads .height.
- After OpenVATEditor finishes, a new FixupAnimationUniforms pass
walks every *_mat.mat in the bake folder and patches `_speed`
(0→1) and `_resolutionY` (from the live Texture2D.height).
Verified end-to-end:
→ VATAssetPostprocessor: normalized texture settings on
Assets/OpenVATContent/rumba_vat.png
→ FixupAnimationUniforms: patched rumba_mat.mat →
_speed=1, _resolutionY=142
→ build SUCCEEDED (125875337 bytes)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…liable Even with _UseTime=1, _UsePackedNormals=1, _speed=1, _frames=71, _resolutionY=142 all correctly set on the OpenVAT material, the dancer stayed in a static texturized T-pose under Unity 6 + URP standalone player. The shadergraph's _Time-driven internal frame math isn't ticking visibly in the player. Add an OpenVATDriver MonoBehaviour that ticks `_frame` per Update from C#: collects every Material with an `_frame` property in the hierarchy, forces _UseTime=false (so the shader reads `_frame` instead of `_Time`), and writes _currentFrame %= frames every tick. This sidesteps the shadergraph self-tick entirely. Wired into CLISetupURPAndBuild: after instantiating the OpenVAT prefab into the scene, attach OpenVATDriver(fps=30, frames=71). Also patches the shader-keyword approach in the same place: the material's `_USETIME_ON` keyword landed in `m_InvalidKeywords` after EnableKeyword(), confirming the shader uses a uniform branch rather than a static branch. The driver makes that moot by writing the uniform we actually want — _frame — directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…checkpoint
The URP + OpenVAT path now produces a fully-textured T-pose dancer
with all 11 submeshes rendering correctly — major progress over the
egg-shaped BiRP rendering. What's still not working: visible
animation playback. The dancer stays static at frame 0 despite:
- _UseTime=1, _UsePackedNormals=1, _exaggeration=1
- _speed=1, _frames=71, _resolutionY=142
- _openVAT_main bound (5828×142 RGBA64)
- _minValues / _maxValues populated from the sidecar JSON
- OpenVATDriver verified at runtime: "bound 11 VAT materials,
fps=30, frames=71" + writes _frame every Update
This appears to be a deeper integration with how OpenVAT's URP
Shader Graph internally wires _UseTime / _frame to the position
deformation amplitude — pushing the right uniforms from C# (both
material params AND _frame per Update) isn't translating to visible
vertex displacement.
README now documents the URP status explicitly + the bake settings
confirmed correct + the suggested next-step investigations (open in
editor for Frame Debugger, try the full `openVAT_decoder.shadergraph`
instead of `_basic`, etc.).
Adds a print() inside OpenVATDriver.Update so the first 5 frames of
playback log the _frame value being pushed — visible in the player
log, useful confirmation the driver is alive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…at runtime
Even with the material asset's `_UseTime = 0` and `_exaggeration = 1`
set in the editor pipeline, ShaderGraph's variant cooking may have
locked in the `_USETIME_ON` keyword variant, making the shader read
`_Time` instead of `_frame` regardless of what the C# uniform writes
say. Two safety nets:
- Driver Start() calls `m.DisableKeyword("_USETIME_ON")` on every
bound material — forces the shader's keyword multi_compile path
back to the `_frame`-reading variant.
- Driver Start() re-checks `_exaggeration` and sets it to 1.0 if
zero — protects against the ShaderGraph cooker resetting it.
This is the runtime mirror of the editor-time fixups in
CLISetupURPAndBuild.FixupAnimationUniforms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…'s vertex output is unwired
ROOT CAUSE of the static-T-pose-with-correct-texture symptom: the
upstream OpenVAT package's `openVAT_decoder.shadergraph` (and the
`_basic` variant) declare `VertexDescription.Position` as a block,
but no graph edge connects the OpenVAT_standard sampling subgraph's
output to that block. So nothing the material's _frame / _exaggeration
/ _UseTime / _speed values do can affect the rendered vertex position —
the graph silently passes IN.positionOS through to VertexDescription.
Position uses its default-input behavior.
Confirmed by tracing the shadergraph JSON: VertexDescription.Position's
slot GUID `591246239e8d4c5b91d9b66f4b306e67` appears exactly twice in
both decoder graphs — once for the BlockNode definition and once for
the slot itself. No edges reference it. The OpenVAT subgraph computes
the deformation, then throws it away.
Replace with `Assets/Shaders/OpenVAT_URP.shader` — a small URP HLSL
shader that mirrors OpenVAT's property layout (so OpenVATEditor's
material setup carries over: _openVAT_main, _minValues, _maxValues,
_frame, _frames, _speed, _UseTime, _UsePackedNormals, _exaggeration,
_resolutionY, plus the standard PBR slots) and DOES wire the
texture-sampled position into the vertex output via
`VertexPositionInputs vpi = GetVertexPositionInputs(finalPos)`.
CLISetupURPAndBuild now runs a SwapToCustomHlslShader pass after
OpenVATEditor.ProcessOpenVATContent finishes: every *_mat.mat in the
bake folder gets `mat.shader = Shader.Find("QtMeshEditor/OpenVAT_URP")`.
All the property values OpenVATEditor populated (texture bindings,
bounds, frame count) carry over to the new shader without
re-assignment.
Vertex-id-to-column-mapping uses SV_VertexID, which lines up correctly
because the custom QtmGltfImporter preserves the bake's vertex order
across the concatenated 11-primitive mesh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shader now animates (since the previous commit wired the VAT deformation to VertexDescription.Position) but renders as the characteristic egg-shaped blob — every vertex samples wrong column. Root cause: `SV_VertexID` gives the vertex's position in the rendered vertex buffer, NOT the bake's column index. The two would line up IF the asset went through my QtmGltfImporter directly, but OpenVATEditor calls `Instantiate(LoadAssetAtPath<GameObject>(modelPath))` which takes a route that may renumber verts. Read the column index from UV2 instead — the `qtmesh vat --emit-uv2` flag writes the per-vertex column index as TEXCOORD_1, which rides with each vertex through any reorder. UV2.x = column, UV2.y = row block (almost always 0 for single-row bakes). This is the same trick the Godot and Unreal demos use. Should have done it from the start. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URP build with the new HLSL shader animates the dancer correctly via _frame, and the head appears to render (column indices 0..1023 are FP16-safe), but column indices >= 1024 still seem to land in wrong spots — egg-shape body with arm-like protrusions where the head sits. Add a runtime diagnostic at OpenVATDriver Start that dumps the mesh's UV2 range + unique X-value count + a few sample positions. If the runtime mesh's UV2 still has values 0..5827 with 5828 unique X's, the problem is downstream (shader sampling). If the runtime UV2 is quantized / clamped / collapsed somehow, the problem is upstream (importer or material cooking). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… quantization Runtime diagnostic confirmed UV2 reaches the CPU-side mesh.uv2 array intact (5828 unique X values, range [0..5827]), yet the dancer still renders as an egg with only the head + a few arm verts in the right spot — exactly the pattern from FP16 quantization (values < ~1024 are FP16-safe; 1024+ snap to nearest 2, 2048+ to nearest 4 etc.). URP under Metal silently downcasts UV channels to half-float during the vertex shader's input attribute fetch, even when: - VertexChannelCompressionMask = 3974 (UV0/1/2/3 cleared) - SetVertexBufferParams declares TexCoord1 as Float32x2 - The cooked Mesh.uv2 array has Float32 values The cleanest workaround: never let UV2 carry integers larger than 2048. Normalize at import time (divide by maxU+1, so all values land in [0,1]) — FP16 represents [0,1] perfectly across the full range. Importer now divides every UV1 entry by (maxU+1) when maxU is large (> 1.5), then mesh.SetUVs(1, ...) writes the normalized floats. The shader already has the heuristic `if (colF <= 1.0001) colF *= texW`, which multiplies the normalized [0,1] back up to the actual column index before sampling. This is the same trick the openvat reference shader (gdshader/usf) uses: ship UV1 as float UVs, not raw integer texel indices. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User's runtime UV2 log still showed range=[0..5827] after the "normalize UV1 by 5828" code was added — Unity's ScriptedImporter cache reused the previous import result because the .gltf file's content hadn't changed, only its importer's source code. Bumping the [ScriptedImporter(version: ...)] attribute invalidates the cache for every asset using that importer and forces a fresh import on next domain reload. Now the normalize-to-[0,1] path applies to the Rumba mesh's UV2 without needing a manual reimport click. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UV1 is now normalized to [0,1] in the cooked Mesh (importer logs
`UV1 range u=[0..0.9998284]` after the version bump), but rendering
is still an egg-shape. FP16 in [0,1] has ~2048 unique values, which
for 5828 columns means every ~3 columns map to the same GPU-side
value → still quantized → still egg.
Replace the fragment output temporarily with a red→cyan gradient
based on uv2.x. If the dancer's body shows:
- SMOOTH continuous gradient (e.g. red on left, cyan on right) →
UV2 reaches the GPU per-vertex correctly. Bug is in the texture
sampling (Metal's Load semantics, half-precision sampling, etc.).
- CHUNKY bands with visible stair-stepping → FP16 quantization is
real and we need to encode the column index across multiple
channels.
After diagnosing, the next commit will revert this and apply the
correct fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… few unique values
Diagnostic confirmed by user's screenshot: with UV2 normalized to
[0,1] AND the shader's fragment painting by uv2.x, the dancer's
body shows CHUNKY red+green BANDS instead of a smooth red→cyan
gradient. URP/Metal quantizes UV channels to FP16 at the vertex
input fetch — and FP16 in [0,1] only has ~2048 unique values,
which is fewer than our 5828 columns even with normalization. Every
~3 verts gets the same column → egg shape.
Switch to vertex Color (R8G8B8A8_UNorm = 32 lossless bits over the
4 channels). Pack the column index into R+G as a 16-bit
little-endian integer (R = col & 0xFF, G = (col >> 8) & 0xFF), and
row block into B. Unity's vertex Color attribute is always uploaded
to the GPU as bytes without any compression — it's the lossless
channel for this kind of payload. 16 bits gives a max column of
65535 vs our 5828, plenty of headroom for future larger bakes.
Importer:
- QtmGltfImporter.cs bumps to version 3 to force re-import.
- SetVertexBufferParams adds VertexAttribute.Color UNorm8 channel.
- After concatenating UV1, build a Color32[] with the packed
column index and assign mesh.colors32 = ....
- Keep UV1 around for external tooling that still reads it.
Shader:
- Add `float4 color : COLOR;` to Attributes.
- vert() unpacks: `colLow = color.r * 255`, `colHigh = color.g *
255`, `colF = colLow + colHigh * 256`, `rowBlk = color.b * 255`.
- Pass the unpacked (col, row) as a float2 into the existing
SampleVATPosition() so the rest of the math stays untouched.
- Drop the `<= 1.0001 → multiply by texWidth` heuristic — the
Color attribute always delivers raw integers now.
- Restore the proper diffuse-textured fragment output (the
red/green diagnostic was temporary).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dancer is still an egg after switching to vertex Color32 encoding. Two possibilities: 1. The Color32 channel didn't get written by the importer (or got stripped during the OpenVATEditor → prefab → instantiate path). 2. The Color channel IS reaching the GPU but with banded/quantized values just like the UV channels. Add two diagnostics: - OpenVATDriver runtime: dump `mesh.colors32` length, unique unpacked count, max unpacked value. Confirms whether the encoding survived the asset pipeline to runtime. - Shader fragment: paint by Color-unpacked colF as red→cyan gradient. Smooth = Color is lossless end-to-end. Chunky = Color is also quantized on the GPU side (which would be deeply weird but possible under URP/Metal). The vert() still computes the VAT displacement, so the dancer's silhouette and motion (or lack thereof) is preserved while the fragment shows the diagnostic colour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e decode breaks Color32 confirmed lossless end-to-end at CPU (runtime log: "5828 unique unpacked column values, max=5827, c[5000]=5000") yet the fragment-painted gradient was still chunky. Realised the bands are NOT FP16 quantization — they're the EXPECTED visualisation of per-vertex UV2 interpolation across triangles (adjacent triangles share verts with wildly different column ids → big color jumps across triangle boundaries). New diagnostic: in the vertex shader, sample _openVAT_main at the unpacked column (frame 0, row 0) and pass the RAW sampled RG to the fragment. Then: - If the dancer shows varied per-region colors that match what frame 0 of the bake should look like, vertex texture sampling works and the bug is in our bounds-decode math or the position output. - If the dancer shows solid color (or a single color per submesh), the texture sampling itself isn't varying per-vertex on the GPU. Also temporarily drives the dancer's position from the sampled (R,G,B) via the bounds lerp — same SampleVATPosition logic inlined — so the deformation is visible alongside the diagnostic color. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After a deep debugging session — Color32 encoding, FP16 quantization investigation, shader graph edge tracing, runtime diagnostics — landed on a definitive root cause: Unity 6 on Apple Silicon Metal doesn't bind the position texture as a vertex-stage resource. The vertex shader's `SAMPLE_TEXTURE2D_LOD` calls return the same texel for every vertex regardless of input UV. The diagnostic that proved it: a fragment shader painting by the sampled RG channels showed a SMOOTH pastel gradient across the mesh — if texture sampling varied per-vertex, we'd see 5828 chaotic per-vert colors; instead we see one continuous bilinear-interpolated gradient. The sampled value is constant across the whole mesh. README's Status section now reflects: - What we proved is working (everything except GPU texture fetch). - The single remaining failure mode (vertex texture fetch). - Specific next-step paths someone could take (compute shader pre-decode, StructuredBuffer with SV_VertexID, switch to DirectX/ Vulkan/HDRP). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vertex texture fetch is broken
Diagnostic from the previous session: a smooth pastel gradient across
the dancer's egg-shape proved Unity 6 + Metal on Apple Silicon does
NOT bind textures to the vertex stage — every vertex's
SAMPLE_TEXTURE2D_LOD returns the same texel regardless of input UV.
The workaround is to switch the Graphics API on the Standalone OSX
build target away from Metal. OpenGLCore is the only other macOS
backend Unity 6 supports, and its vertex shader path does handle
texture fetch correctly per-vertex.
`ForceOpenGLCoreOnMac()` runs on every CI build via
`PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneOSX,
new[] { GraphicsDeviceType.OpenGLCore })` + the matching
`SetUseDefaultGraphicsAPIs(target, false)` to disable Unity's
"platform default" fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WebGL bypasses the Unity 6 + Apple Silicon + Metal vertex-stage texture-fetch bug that's blocking the macOS Standalone build. WebGL builds compile shaders to GLSL ES 3 / WebGL 2, which honours vertex-stage SAMPLE_TEXTURE2D_LOD correctly per-vertex. New entry point: invoke as Unity ... -executeMethod QtMeshEditor.VAT.Editor.CLISetupURPAndBuild.RunWeb Same setup (Linear color space, URP asset, OpenVAT material build) just builds to BuildTarget.WebGL into tools/unity-vat-demo/Build/Web/ instead of Build/Mac/. Switches the active build target via EditorUserBuildSettings.SwitchActiveBuildTarget() — required because BuildPlayer needs the platform-specific support module loaded into the editor context. Also documents ForceOpenGLCoreOnMac as a no-op + the reasoning in the comment (Unity 6 forces Metal on ARM64 Mac builds regardless of the API list; the x64-via-Rosetta workaround introduces a Rosetta dependency that WebGL avoids). Requires: WebGL Build Support module installed via Unity Hub (separate ~500 MB download — `unityhub://6000.4.8f1/.../webgl` URL scheme triggers the install prompt automatically). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, scene Picks up the editor-written changes from the last successful WebGL build attempt: - Assets/Shaders/OpenVAT_URP.shader.meta — Unity assigned a GUID to the custom HLSL shader on first import. Commit it so the material (and any future references) stay stable across machines. - ProjectSettings/URPProjectSettings.asset — auto-generated when URP installed. - ProjectSettings/QualitySettings.asset + GraphicsSettings.asset — URP wiring + the OpenGL/Metal API list (kept as documentation of what we tried, even though the macOS standalone Metal path remains blocked). - ProjectSettings/ProjectSettings.asset — color space → Linear, VertexChannelCompressionMask, Standalone arch settings. - Assets/OpenVATContent/rumba_mat.mat — the patched material with _UseTime=1, _exaggeration=1, _frames=71, _resolutionY=142, _USETIME_ON keyword, Color32 channel bound. - Assets/Scenes/OpenVATWeb.unity — the dancer prefab + OpenVATDriver + camera + light layout, scene-driven by CLISetupURPAndBuild. These ride along with the WebGL build pipeline added in the previous commit (1611415). With the WebGL Build Support module already installed, `Unity ... -executeMethod CLISetupURPAndBuild.RunWeb` produces a ~20 MB WebGL bundle under Build/Web/ that runs in any browser with WebGL 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s can serve it User got "Unable to parse Build/Web.framework.js.gz" in the browser when loading the first WebGL build via `python3 -m http.server`. The default WebGL compressionFormat is Gzip — Unity writes Web.wasm.gz + Web.data.gz + Web.framework.js.gz and the loader expects the HTTP server to send `Content-Encoding: gzip` so the browser decompresses in transit. Python's stdlib http.server doesn't do that; it just delivers the .gz file literally and the WebGL loader chokes. Set `PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Disabled` in BuildWebPlayer. Trade-off: build grew from 20 MB → 70 MB on disk (Web.wasm 53 MB vs 13 MB gzipped) but the build runs on any static-file server with zero configuration. For production hosting on a real web server (nginx, caddy, etc.) the Gzip or Brotli compression option is the right choice — but for local-dev / repo-shipped demos, Disabled is the sane default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diagnostic confirmed by the previous screenshot: when the shader painted the dancer purely by raw IN.color (no texture sampling involved), the mesh showed chaotic per-vertex red/orange/green variation — proving Vertex Color reaches the GPU per-vertex correctly on URP/WebGL. The remaining issue was the texture sampling itself — SAMPLE_TEXTURE2D_LOD with normalized UVs was collapsing all vertices to the same texel (or at most 4 corners), giving the smooth pastel egg gradient we'd seen. Switch to LOAD_TEXTURE2D_LOD with integer texel coordinates. LOAD goes through GL's texelFetch which bypasses any sampler-state-driven interpolation/quantization and reads exactly the texel at (col, row). Combined with the Vertex Color unpacking that gives us a true 16-bit column index per vertex, this should finally produce correct per-vertex sampling and animate the Rumba dancer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…P's gamma decode
THE final missing piece. URP under Linear color space (which we use)
automatically applies sRGB→Linear conversion on vertex Color
channels during upload. That destroys our byte-packed integer
payload:
col=100 packed as r=100/255=0.392
→ URP sRGB→Linear → 0.127
→ unpacked as col=int(0.127*255)=32 (NOT 100)
Every column value gets monotonically squashed through the gamma
curve, multiple distinct verts collapse to the same column → the
confetti egg we just saw with LOAD_TEXTURE2D.
Apply the inverse transform (Linear→sRGB) in the vertex shader
before unpacking. Recovers the original byte values exactly:
float3 LinearToSRGB(float3 lin)
{
float3 lo = lin * 12.92;
float3 hi = 1.055 * pow(abs(lin), 1.0/2.4) - 0.055;
return lerp(lo, hi, step(0.0031308, lin));
}
With this, the chain becomes:
importer: col → r=col_low/255, g=col_high/255 (linear bytes)
URP upload: r → sRGB→Linear (corrupted)
shader: LinearToSRGB(IN.color.rgb) → back to linear bytes
unpack: colF = r*255 + g*255*256 → original col
Result: vertex Color delivers the correct 16-bit unsigned integer
per vertex, LOAD_TEXTURE2D samples the right texel per vertex,
and the Rumba dancer should finally animate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… to v4
The Linear→sRGB inverse I added in the previous commit produced an
empty scene — every vertex collapsed to a single point. The gamma
theory was wrong: URP doesn't apply sRGB→Linear on vertex Color
channels when they're declared as Color32 (UNorm8). The IN.color.r
values arrive in [0,1] linearly mapped from the byte values.
What was actually broken: the importer had two stale versions
fighting each other across reimports. The PREVIOUS code path
normalized UV1 to [0,1] (divide by maxU+1), then later code paths
removed the normalization but Unity cached the previous imported
result. The runtime UV1 diagnostic logging `u=[0..0.9998]` proved
the cached Color32 was packed from the normalized-tiny-float UV1
values → col = RoundToInt(0.99) = 1 for nearly every vertex →
every vertex sampled the same texel.
Bump QtmGltfImporter [ScriptedImporter(version: 4)] to force a
fresh re-import. Build log now confirms:
QtmGltfImporter: encoded column index into vertex Color32
(max col was 5827)
QtmGltfImporter: UV1 range u=[0..5827], v=[0..0]
(must be integer column indices in [0..texWidth-1])
Drops the gamma round-trip from OpenVAT_URP.shader and the
LinearToSRGB() helper — Color delivery is straightforward
linear bytes after all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ate LinearToSRGB
The previous build had a hidden shader-compile error:
Shader error in 'QtMeshEditor/OpenVAT_URP': redefinition of
'LinearToSRGB' at OpenVAT_URP.shader(148)
URP's Core.hlsl already defines LinearToSRGB(), and my duplicate
shadowed it on gles3 → the shader failed to compile → Unity fell
back to a stub that draws nothing → empty scene.
Drop the duplicate (we're not using gamma transform anyway).
Replace the vertex shader with a minimal diagnostic:
- vert: bind-pose verts (clear T-pose silhouette), sample
position texture at (col, row=0) via LOAD_TEXTURE2D, pass
sampled.rg through varyings.
- frag: paint by sampled.r (red), sampled.g (green), 0.5 (blue).
If we see varied per-vertex color across the dancer matching the
bake's frame-0 distribution, LOAD_TEXTURE2D works per-vertex. If
solid or smooth, sampling collapsed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ally animate
Diagnostic confirmed: LOAD_TEXTURE2D + vertex Color column index
delivers per-vertex texel sampling correctly on URP / WebGL2. The
previous diagnostic frame showed a static T-pose dancer painted with
smoothly-varied per-vertex pastel colors — each vertex pulled its
own texel from frame 0 of the position texture and the fragment
shader visualised it.
Now wire that to actual position output:
- vert(): unpack column from IN.color, pick current frame from
_Time or _frame, sample (col, frameRow) and (col, frameRow+frames)
via LOAD_TEXTURE2D_LOD, decode position+normal, lerp bindPose →
vatPose by _exaggeration.
- frag(): standard URP lit with diffuse texture.
This is the same logic as the canonical Godot/Unreal demos, just
adapted to URP HLSL — finally producing animation now that we
identified vertex texture fetch via SAMPLE was the bug and LOAD is
the fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The WebGL path got us past the Metal vertex-stage-texture-fetch wall: LOAD_TEXTURE2D + Color32-packed column index proved by diagnostic to sample per-vertex correctly. But the actual VAT replay still gives a chaotic colored egg — each vertex samples a column that isn't its own. Suspected: OpenVATEditor's Instantiate(prefab) chain renumbers verts somehow between QtmGltfImporter (which packs col=N into Color32[N]) and the renderer (which sees Color32[M] for the vert that should have col=N). README updated with the current status, what's confirmed working end-to-end, and two concrete suggestions for a future session: runtime MeshDataArray-based cooker, or StructuredBuffer<int> indexed by SV_VertexID. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batched code-review fixes across the Unity demo:
1. VATAssetPostprocessor.cs — InBakeDir() now requires kBakeDir +
"/" with StringComparison.Ordinal, so `Assets/VAT/Rumba_backup`
no longer falsely matches the `Assets/VAT/Rumba` watch.
2. QtmGltfImporter.cs (buffer URI) — canonicalize and enforce
directory containment on buffer URIs. A malicious `../`-style
URI in an imported glTF would otherwise let the importer read
arbitrary local files. LogImportError + return on escape.
3. QtmGltfImporter.cs (accessor layout) — ReadVec2/ReadVec3 now
validate accessor `type` (VEC2/VEC3) and `componentType` (5126
float32), and reject `bufferView.byteStride != elementSize`
(interleaved layouts). Throws an explicit exception instead of
silently decoding garbage.
4. CLIBuildScenes.cs (PerfSkeleton) — `BuildAll` no longer drops
PerfSkeleton.unity from EditorBuildSettings.scenes. If the file
exists, append it as a third entry.
5. CLIBuildScenes.cs (fail-fast assets) — `BuildPerfVAT` throws
InvalidOperationException listing missing assets if sourceMesh
/ positionTexture / diffuseTexture come back null. Saves us
from a "successful" build of a broken scene.
6. CLIBuilder.cs — EditorApplication.Exit(1) when no scenes found
(was 0). Headless CI now fails the build instead of "passing"
with an empty player.
7. FPSOverlay.cs — OnValidate clamps windowFrames to ≥1, Update
double-checks at runtime. A negative value in the Inspector no
longer causes a queue-underflow exception every frame.
8. PerfSpawnerVAT.cs — guard `phaseJitter > 0f` before the modulo.
A zero or negative inspector value produced NaN phases that
broke every spawned instance simultaneously.
9. BootstrapVAT.cs — TryParseSidecar now requires Min AND Max to
parse successfully (in addition to Frames). Previously a
sidecar with missing Min/Max bounds would silently apply
(0,0,0) bounds and break the VAT remap. Renamed
ParseVec3Field → TryParseVec3Field and uses float.TryParse with
InvariantCulture for deterministic results across locales.
10. VATPlayer.cs — guard every Material property write with
HasProperty() so swapping shaders (BiRP, URP HLSL, ShaderGraph
variants) doesn't emit "shader doesn't have property X"
warnings per instance per frame.
Skipped reviews:
- chatgpt-codex on manifest.json:4 (add gltFast) — superseded
by our QtmGltfImporter ScriptedImporter that owns .gltf imports
directly; adding gltFast now would conflict.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLIPipelineCmdTest suite re-imports `Twist Dance.fbx` inside every test case via MeshImporterExporter::importer + Ogre. With ~50 tests at ~20s/import-cycle on the GitHub-Linux runner that's 4–6 min of legitimate work, right at the prior 5-min cap. We just saw an intermittent SIGKILL of the suite mid-test: [ RUN ] CLIPipelineCmdTest.CmdAnimRename_SameNameNoop WARNING: Suite CLIPipelineCmdTest CRASHED (signal 9) The same test passed in 21.8s on the prior run, so it's not a genuine deadlock — just the suite-level total wallclock crossing 300s. Bump PER_SUITE_TIMEOUT to 600s. Still tight enough to catch real deadlocks (a hung Qt event loop never finishes anyway), generous enough to absorb runner variance. Job-level `timeout-minutes: 45` outer guardrail stays the same; the full suite normally completes in ~12 min, well under 45. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|



Summary
tools/unity-vat-demo/, a stock-Unity-API port of the existing Godot/Unreal VAT demos. Same Rumba bake, same shader contract — useful when integrating aqtmesh vatbake into a Unity 2022.3 LTS+ project.Assets/Shaders/openvat.shader, the Rumba bake underAssets/VAT/Rumba/, and a Unity project skeleton (ProjectSettings, Packages/manifest.json, .gitignore).qtmesh vatsection links Godot/Unity/Unreal demos side by side.Test plan
tools/unity-vat-demo/in Unity Hub → Add → 2022.3 LTS. First import succeeds.mixamo.com_pos.pngtexture import to sRGB=OFF, Filter=Point, Compression=None, Wrap=Clamp.source.gltfmodel Read/Write=ON.min(1s) FPSis higher than the skeleton scene's on the same machine.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores