OpenVAT in VRChat: Unterschied zwischen den Versionen

Aus Gargi.org
Zur Navigation springen Zur Suche springen
Zeile 203: Zeile 203:


== 14. Zusatztool: PingPong Looper ==
== 14. Zusatztool: PingPong Looper ==
[[File:vat_pingpong_looper.zip|Download Blender Addon]]


Dieses Add-on dupliziert die Animation, kehrt sie um und hängt sie an.  
Dieses Add-on dupliziert die Animation, kehrt sie um und hängt sie an.  
Zeile 210: Zeile 208:


Ideal für VAT-Animationen wie Flaggen oder Simulationen.
Ideal für VAT-Animationen wie Flaggen oder Simulationen.
Code für das Add-on


<div style="max-height:400px; overflow:auto; border:1px solid #ccc; padding:10px;">
<div style="max-height:400px; overflow:auto; border:1px solid #ccc; padding:10px;">
Zeile 215: Zeile 215:
<pre>bl_info = {
<pre>bl_info = {
     "name": "VAT PingPong Looper",
     "name": "VAT PingPong Looper",
     "author": "ChatGPT und Pierre",
     "author": "Pierre",
     "version": (1, 0, 0),
     "version": (1, 0, 0),
     "blender": (5, 0, 0),
     "blender": (5, 0, 0),

Version vom 4. Juli 2026, 22:36 Uhr

OpenVAT in VRChat (Unity Built-in)

Einleitung

OpenVAT (Vertex Animation Textures) ist eine Technik, bei der Animationen nicht über Bones, sondern über Texturen abgespielt werden. Dabei werden Vertex-Positionen in einer EXR-Datei gespeichert und im Shader wieder ausgelesen.

Das ist besonders interessant für VR, da:

Animationen komplett auf der GPU laufen keine komplexen Rigs benötigt werden Simulationen wie Stoff oder Flaggen sehr performant dargestellt werden können

1. OpenVAT in Blender verwenden

Installation von OpenVAT

Bevor Vertex Animation Textures erstellt werden können, muss zunächst das kostenlose Blender-Add-on OpenVAT installiert werden. OpenVAT wird aktiv weiterentwickelt und ist offiziell für Blender 4.2 oder neuer verfügbar.

Download

OpenVAT kann auf zwei Arten installiert werden:

Blender Extensions (empfohlen)

GitHub-Projekt

Installation über Blender Extensions

Blender starten. Edit → Preferences öffnen. Zum Bereich Get Extensions wechseln. Nach OpenVAT suchen. Auf Install klicken.

Alternativ kann das Add-on auch über die Blender Extensions Webseite heruntergeladen und anschließend über Edit → Preferences → Add-ons → Install... als ZIP-Datei installiert werden.

Jetzt eine Animation erstellen (z. B. Cloth Simulation)

2. OpenVAT auf das Objekt anwenden

Wichtige Einstellungen:

Frame Range – Animationsbereich Vertex Data: Standard (Position/Normal) Deformation Basis: Start Frame → wichtig für Offset-Animation Transform: Object Use Single Row → optimiert Texture Layout

Dann auf Encode Vertex Animation Texture klicken.

3. Shader Graph VRC installieren


Im VRChat Creator Companion:

Repository hinzufügen:

 https://z3y.github.io/vpm-package-listing/index.json 

Danach:

Paket Shader Graph VRC installieren im Projekt aktivieren

Warum: → Standard ShaderGraph ist nicht VRChat-kompatibel → Dieses Paket macht Built-in Shader nutzbar

4. Shader Graph in Unity installieren

Im Unity Package Manager:

Shader Graph installieren

5. OpenVAT Plugin installieren

Im Package Manager:

Add package from Git URL

 https://github.com/sharpen3d/openvat-unity.git 

6. Export aus Blender importieren

Alle Dateien in Unity kopieren:

FBX (Mesh) EXR (Animation) JSON (optional)

Empfohlen: → eigenes Verzeichnis erstellen

7. EXR Texture korrekt einstellen

Die EXR ist keine normale Textur, sondern enthält Vertex-Daten!

Wichtige Einstellungen:

Non Power of 2 = None Compression = None MipMaps = Off Wrap Mode = Clamp Filter Mode = Point (oder Bilinear für smoothing)

Sehr wichtig:

Für jede Plattform separat einstellen:

Windows

Android (Quest!)

iOS

→ Unity nutzt Plattform-Overrides → falsche Settings zerstören die Animation oder Qualität

8. Shader auf VRChat Built-in umstellen

Im ShaderGraph:

Graph Inspector öffnen Active Targets → Built-In VRC

Warum:

VRChat nutzt die Built-in Pipeline → URP funktioniert nicht

9. OpenVAT Tool ausführen

Im Menü:

Tools → OpenVAT Editor

Dann:

Export-Ordner auswählen Process OpenVAT Content klicken

10. Ergebnis

Unity erzeugt automatisch:

Material Prefab aufbereitetes Mesh

11. Albedo und Emission hinzufügen

Im ShaderGraph ergänzen:

Texture2D Property erstellen (Albedo) Sample Texture 2D Node UV0 anschließen Output → Base Color

Optional Emission:

Texture → Multiply (z. B. Stärke 1–3) Output → Emission

12. Textur im Material setzen

Im Material:

Albedo Textur zuweisen

13. Prefab testen

Prefab in die Szene ziehen:

Animation sollte direkt laufen Funktion testen

14. Zusatztool: PingPong Looper

Dieses Add-on dupliziert die Animation, kehrt sie um und hängt sie an. Dadurch entsteht ein nahtloser Loop ohne sichtbaren Übergang.

Ideal für VAT-Animationen wie Flaggen oder Simulationen.

Code für das Add-on

bl_info = {
    "name": "VAT PingPong Looper",
    "author": "Pierre",
    "version": (1, 0, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar > VAT Loop",
    "description": "Creates eased ping-pong Shape Key animation from simulated mesh frames for OpenVAT",
    "category": "Animation",
}

import bpy


def smoothstep(t):
    return t * t * (3.0 - 2.0 * t)


def iter_action_fcurves(action):
    if action is None:
        return []

    if hasattr(action, "fcurves"):
        return action.fcurves

    fcurves = []

    if hasattr(action, "layers"):
        for layer in action.layers:
            for strip in layer.strips:
                if hasattr(strip, "channelbags"):
                    for channelbag in strip.channelbags:
                        if hasattr(channelbag, "fcurves"):
                            fcurves.extend(channelbag.fcurves)

    return fcurves


def build_eased_pingpong_frames(start, end, ease_frames=12, hold_frames=0):
    frames = []

    normal_end = max(start, end - ease_frames)

    frames.extend(range(start, normal_end + 1))

    for i in range(1, ease_frames + 1):
        t = i / ease_frames
        eased = smoothstep(t)
        frame = normal_end + eased * (end - normal_end)
        frames.append(round(frame))

    frames.extend([end] * hold_frames)

    for i in range(1, ease_frames + 1):
        t = i / ease_frames
        eased = smoothstep(t)
        frame = end - eased * (end - normal_end)
        frames.append(round(frame))

    backward_start = normal_end - 1
    frames.extend(range(backward_start, start, -1))

    cleaned = []

    for f in frames:
        if not cleaned or f != cleaned[-1]:
            cleaned.append(f)
        elif f == end:
            cleaned.append(f)

    return cleaned


class VATLoopSettings(bpy.types.PropertyGroup):
    source_start: bpy.props.IntProperty(
        name="Start Frame",
        default=1,
        min=1
    )

    source_end: bpy.props.IntProperty(
        name="End Frame",
        default=200,
        min=2
    )

    ease_frames: bpy.props.IntProperty(
        name="Ease Frames",
        default=5,
        min=0
    )

    hold_frames: bpy.props.IntProperty(
        name="Hold Frames",
        default=0,
        min=0
    )

    output_start: bpy.props.IntProperty(
        name="Output Start",
        default=1,
        min=1
    )

    keep_source: bpy.props.BoolProperty(
        name="Hide Source Object",
        default=True
    )

    object_name: bpy.props.StringProperty(
        name="New Object Name",
        default="VAT_PingPong_Loop"
    )


class VATLOOP_OT_loop_me(bpy.types.Operator):
    bl_idname = "vatloop.loop_me"
    bl_label = "Loop me"
    bl_description = "Create eased ping-pong Shape Key animation from selected mesh"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        settings = context.scene.vat_loop_settings
        src = context.active_object

        if src is None or src.type != "MESH":
            self.report({"ERROR"}, "Bitte zuerst ein Mesh-Objekt auswählen.")
            return {"CANCELLED"}

        if settings.source_end <= settings.source_start:
            self.report({"ERROR"}, "End Frame muss größer als Start Frame sein.")
            return {"CANCELLED"}

        scene = context.scene
        depsgraph = context.evaluated_depsgraph_get()

        frames = build_eased_pingpong_frames(
            settings.source_start,
            settings.source_end,
            settings.ease_frames,
            settings.hold_frames
        )

        scene.frame_set(frames[0])
        context.view_layer.update()

        src_eval = src.evaluated_get(depsgraph)
        base_mesh = bpy.data.meshes.new_from_object(src_eval, depsgraph=depsgraph)

        new_obj = bpy.data.objects.new(settings.object_name, base_mesh)
        context.collection.objects.link(new_obj)

        bpy.ops.object.select_all(action="DESELECT")
        context.view_layer.objects.active = new_obj
        new_obj.select_set(True)

        new_obj.shape_key_add(name="Basis")

        shape_keys = []

        for i, src_frame in enumerate(frames):
            scene.frame_set(src_frame)
            context.view_layer.update()

            src_eval = src.evaluated_get(depsgraph)
            temp_mesh = bpy.data.meshes.new_from_object(src_eval, depsgraph=depsgraph)

            if len(temp_mesh.vertices) != len(base_mesh.vertices):
                bpy.data.meshes.remove(temp_mesh)
                self.report({"ERROR"}, f"Vertex-Anzahl hat sich bei Frame {src_frame} geändert.")
                return {"CANCELLED"}

            key = new_obj.shape_key_add(
                name=f"Frame_{i + settings.output_start:04d}_Src_{src_frame:04d}"
            )

            for v_idx, vert in enumerate(temp_mesh.vertices):
                key.data[v_idx].co = vert.co

            key.value = 0.0
            shape_keys.append(key)

            bpy.data.meshes.remove(temp_mesh)

        for i, key in enumerate(shape_keys):
            out_frame = settings.output_start + i

            key.value = 0.0
            key.keyframe_insert("value", frame=out_frame - 1)

            key.value = 1.0
            key.keyframe_insert("value", frame=out_frame)

            key.value = 0.0
            key.keyframe_insert("value", frame=out_frame + 1)

        if new_obj.data.shape_keys and new_obj.data.shape_keys.animation_data:
            action = new_obj.data.shape_keys.animation_data.action
            for fc in iter_action_fcurves(action):
                for kp in fc.keyframe_points:
                    kp.interpolation = "CONSTANT"

        scene.frame_start = settings.output_start
        scene.frame_end = settings.output_start + len(frames) - 1
        scene.frame_set(scene.frame_start)

        if settings.keep_source:
            src.hide_viewport = True
            src.hide_render = True

        self.report(
            {"INFO"},
            f"Loop erstellt: {new_obj.name}, Frames {scene.frame_start}-{scene.frame_end}"
        )

        return {"FINISHED"}


class VATLOOP_PT_panel(bpy.types.Panel):
    bl_label = "VAT PingPong Looper"
    bl_idname = "VATLOOP_PT_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "VAT Loop"

    def draw(self, context):
        layout = self.layout
        settings = context.scene.vat_loop_settings

        layout.label(text="Source Frames")
        layout.prop(settings, "source_start")
        layout.prop(settings, "source_end")

        layout.separator()

        layout.label(text="Loop Smoothing")
        layout.prop(settings, "ease_frames")
        layout.prop(settings, "hold_frames")

        layout.separator()

        layout.label(text="Output")
        layout.prop(settings, "output_start")
        layout.prop(settings, "object_name")
        layout.prop(settings, "keep_source")

        layout.separator()

        layout.operator("vatloop.loop_me", icon="MOD_CLOTH")


classes = (
    VATLoopSettings,
    VATLOOP_OT_loop_me,
    VATLOOP_PT_panel,
)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Scene.vat_loop_settings = bpy.props.PointerProperty(type=VATLoopSettings)


def unregister():
    del bpy.types.Scene.vat_loop_settings

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()

Fazit

OpenVAT ermöglicht:

GPU-basierte Animationen komplexe Simulationen ohne Bones sehr gute Performance in VR

Wichtige Learnings:

EXR darf nicht verändert werden Plattform-Overrides sind entscheidend (Quest!) Shader muss VRChat-kompatibel sein

Damit lassen sich hochwertige Animationen direkt in VRChat einsetzen.