diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-12 20:55:18 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-12 20:55:18 -0800 |
| commit | bd4d3aa6537cb3121cd1eca4a8ea4eb65eb28522 (patch) | |
| tree | 021f4296a91ba890a45b7d9cd920bf473c7bb216 | |
| parent | 6d86c9663bab3ec1ef95ba455dfa7281415b7f44 (diff) | |
Fold: update UI, add plane -> hemioctahedron
| -rw-r--r-- | Scripts/Fold/Editor/FoldPipelineBuilder.cs | 270 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/README.md | 83 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/VertexDeformationBuilder.cs | 339 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/VertexDeformationExamples.cs | 114 | ||||
| -rw-r--r-- | vertex.cginc | 23 | ||||
| -rw-r--r-- | vertex_deformation.slang | 71 |
6 files changed, 416 insertions, 484 deletions
diff --git a/Scripts/Fold/Editor/FoldPipelineBuilder.cs b/Scripts/Fold/Editor/FoldPipelineBuilder.cs new file mode 100644 index 0000000..64032a5 --- /dev/null +++ b/Scripts/Fold/Editor/FoldPipelineBuilder.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +public class FoldSlot +{ + public int opcode; + public float float0, float1, float2, float3; + public Vector4 vec0, vec1, vec2, vec3; + + public void ApplyToMaterial(Material mat, int slotIndex) + { + var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_"; + mat.SetFloat(prefix + "Enabled", 1f); + mat.SetInteger(prefix + "Opcode", opcode); + mat.SetFloat(prefix + "Float_0", float0); + mat.SetFloat(prefix + "Float_1", float1); + mat.SetFloat(prefix + "Float_2", float2); + mat.SetFloat(prefix + "Float_3", float3); + mat.SetVector(prefix + "Vector_0", vec0); + mat.SetVector(prefix + "Vector_1", vec1); + mat.SetVector(prefix + "Vector_2", vec2); + mat.SetVector(prefix + "Vector_3", vec3); + } + + public static void ClearSlot(Material mat, int slotIndex) + { + var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_"; + mat.SetFloat(prefix + "Enabled", 0f); + mat.SetInteger(prefix + "Opcode", 0); + mat.SetFloat(prefix + "Float_0", 0f); + mat.SetFloat(prefix + "Float_1", 0f); + mat.SetFloat(prefix + "Float_2", 0f); + mat.SetFloat(prefix + "Float_3", 0f); + mat.SetVector(prefix + "Vector_0", Vector4.zero); + mat.SetVector(prefix + "Vector_1", Vector4.zero); + mat.SetVector(prefix + "Vector_2", Vector4.zero); + mat.SetVector(prefix + "Vector_3", Vector4.zero); + } +} + +public class FoldPipelineBuilder +{ + readonly List<FoldSlot> slots = new(); + Material targetMaterial; + + public static class Opcodes + { + public const int None = 0; + public const int TubeToPlane = 1; + public const int PlaneToTube = 2; + public const int PointAlign = 3; + public const int AxisAlign = 4; + public const int NormConversion = 5; + public const int Seal = 6; + public const int SineWaves = 7; + public const int FBM = 8; + public const int PlaneToHemiOctahedron = 9; + } + + FoldPipelineBuilder() { } + + public static FoldPipelineBuilder Create() => new(); + + public FoldPipelineBuilder For(Material material) + { + targetMaterial = material; + return this; + } + + public FoldPipelineBuilder TubeToPlane(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.TubeToPlane, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder PlaneToTube(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.PlaneToTube, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder PlaneToHemiOctahedron(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.PlaneToHemiOctahedron, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder PointAlign(Vector3 po, Vector3 pp, Vector3 r, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.PointAlign, + vec0 = po, + vec1 = pp, + vec2 = r, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder AxisAlign(Vector3 po, Vector3 pp, Vector3 r, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.AxisAlign, + vec0 = po, + vec1 = pp, + vec2 = r, + float0 = t + }); + return this; + } + + public FoldPipelineBuilder NormConversion(float inputK, float outputK, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.NormConversion, + float0 = inputK, + float1 = outputK, + float2 = t + }); + return this; + } + + public FoldPipelineBuilder Seal(float A, float k, float st, float t) + { + slots.Add(new FoldSlot + { + opcode = Opcodes.Seal, + float0 = A, + float1 = k, + float2 = st, + float3 = t + }); + return this; + } + + public FoldPipelineBuilder SineWaves() + { + slots.Add(new FoldSlot { opcode = Opcodes.SineWaves }); + return this; + } + + public FoldPipelineBuilder FBM() + { + slots.Add(new FoldSlot { opcode = Opcodes.FBM }); + return this; + } + + public FoldPipelineBuilder Custom(int opcode, + float f0 = 0, float f1 = 0, float f2 = 0, float f3 = 0, + Vector4? v0 = null, Vector4? v1 = null, Vector4? v2 = null, Vector4? v3 = null) + { + slots.Add(new FoldSlot + { + opcode = opcode, + float0 = f0, float1 = f1, float2 = f2, float3 = f3, + vec0 = v0 ?? Vector4.zero, + vec1 = v1 ?? Vector4.zero, + vec2 = v2 ?? Vector4.zero, + vec3 = v3 ?? Vector4.zero + }); + return this; + } + + public void Apply() + { + if (targetMaterial == null) + { + Debug.LogError("No target material set. Use .For(material) before .Apply()"); + return; + } + + if (slots.Count > 16) + { + Debug.LogWarning($"Too many operations ({slots.Count}). Only the first 16 will be applied."); + } + + Undo.RecordObject(targetMaterial, "Apply Vertex Deformation"); + + targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 1f); + + // Clear all slots first to ensure clean state + for (int i = 0; i < 16; i++) + FoldSlot.ClearSlot(targetMaterial, i); + + // Apply active slots + for (int i = 0; i < slots.Count && i < 16; i++) + slots[i].ApplyToMaterial(targetMaterial, i); + + EditorUtility.SetDirty(targetMaterial); + } + + public void Clear() + { + slots.Clear(); + } + + public int Count => slots.Count; +} + +public static class FoldPresets +{ + public static void TubeToPlaneFull(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .Apply(); + + public static void PlaneToTubeFull(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .Apply(); + + public static void PlaneToHemiOctahedronFull(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .PlaneToHemiOctahedron(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .Apply(); + + public static void TubeToPlaneThenNormConv(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(2f, 1f, 1f) + .Apply(); + + public static void NormConvL1ToL2(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .NormConversion(1f, 2f, 1f) + .Apply(); + + public static void NormConvL2ToL1(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .NormConversion(2f, 1f, 1f) + .Apply(); + + public static void NormConvL2ToLinf(Material mat) => + FoldPipelineBuilder.Create() + .For(mat) + .NormConversion(2f, float.PositiveInfinity, 1f) + .Apply(); +} diff --git a/Scripts/Fold/Editor/README.md b/Scripts/Fold/Editor/README.md index 8305abf..d2b7494 100644 --- a/Scripts/Fold/Editor/README.md +++ b/Scripts/Fold/Editor/README.md @@ -1,14 +1,28 @@ -# Vertex Deformation Fluent Builder +# Fold - Vertex Deformation Pipeline Builder -A simple, code-first API for building vertex deformation pipelines without visual node editors. +A visual editor and fluent API for building vertex deformation pipelines. ## Quick Start +### Using the Editor Window + +Open `Tools > yum_food > Fold` to access the dockable pipeline editor: + +1. Select a target material +2. Click "Add Operation" to add deformation operations +3. Configure parameters for each operation +4. Reorder operations with ▲▼ buttons +5. Click "Apply to Material" to write the pipeline to the material + +Use "Load Presets" for common pipelines. + +### Using the Fluent API + ```csharp using UnityEngine; // Apply to a material -VertexDeformationBuilder.Create() +FoldPipelineBuilder.Create() .For(material) .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) .NormConversion(2f, 1f, 1f) @@ -28,6 +42,15 @@ Unfolds a tube into a plane. Folds a plane into a tube. - Same parameters as TubeToPlane +### PlaneToHemiOctahedron(p, r, s, t) +Maps a plane to a hemi-octahedron (half octahedron) shape. +- `p`: Origin point (Vector3) +- `r`: R axis direction (Vector3) +- `s`: S axis direction (Vector3) +- `t`: Interpolation factor (float, 0-1) + +Useful for creating dome-like or hemisphere projections from planar geometry. + ### PointAlign(po, pp, r, t) Aligns geometry to a point. - `po`: Original point (Vector3) @@ -66,47 +89,53 @@ Applies fractal Brownian motion deformation. ### Custom(opcode, f0-f3, v0-v3) For advanced use cases or custom opcodes. -## Presets +## Presets (Code) -Use built-in presets from anywhere: +Use built-in presets from code: ```csharp -VertexDeformationPresets.TubeToPlaneFull(material); -VertexDeformationPresets.NormConvL2ToL1(material); -VertexDeformationPresets.NormConvL2ToLinf(material); +FoldPresets.TubeToPlaneFull(material); +FoldPresets.NormConvL2ToL1(material); +FoldPresets.NormConvL2ToLinf(material); ``` -## GUI Window +## Adding Custom Presets -Open `Window > Vertex Deformation Presets` for a GUI with buttons for common operations. +Edit `FoldEditorWindow.cs` to add presets to the "Load Presets" menu: -## Adding Custom Presets +```csharp +// In ShowPresetsMenu(): +menu.AddItem(new GUIContent("My Custom Effect"), false, () => LoadPreset_MyEffect()); + +// Add the preset loader method: +void LoadPreset_MyEffect() +{ + operations.Clear(); + AddOperation(new TubeToPlaneOp()); + var norm = new NormConversionOp(); + norm.inputK = 2f; + norm.outputK = 1f; + AddOperation(norm); +} +``` -1. Edit `VertexDeformationPresets` class: +Or use the fluent API in code: ```csharp public static void MyCustomEffect(Material mat) => - VertexDeformationBuilder.Create() + FoldPipelineBuilder.Create() .For(mat) .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) .NormConversion(2f, 1f, 0.5f) - .Seal(0.1f, 2f, 0.8f, 1f) .Apply(); ``` -2. Add button in `VertexDeformationEditorWindow`: - -```csharp -if (GUILayout.Button("My Custom Effect")) - VertexDeformationPresets.MyCustomEffect(targetMaterial); -``` - ## Pipeline Chaining Operations are applied in the order they're chained: ```csharp -VertexDeformationBuilder.Create() +FoldPipelineBuilder.Create() .For(material) .TubeToPlane(...) // Step 1 .NormConversion(...) // Step 2 @@ -119,13 +148,9 @@ The shader supports up to 16 operations per material. ## Clearing Deformations -```csharp -// Via GUI: Use "Clear All Deformations" button +In the Fold window, click "Clear All" to remove all operations from the pipeline. -// Via code: +Via code: +```csharp targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f); ``` - -## Examples - -See `VertexDeformationExamples.cs` for menu items under `Tools > Vertex Deformation > Example: ...` diff --git a/Scripts/Fold/Editor/VertexDeformationBuilder.cs b/Scripts/Fold/Editor/VertexDeformationBuilder.cs deleted file mode 100644 index c04a66a..0000000 --- a/Scripts/Fold/Editor/VertexDeformationBuilder.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; - -public class VertexDeformationSlot -{ - public int opcode; - public float float0, float1, float2, float3; - public Vector4 vec0, vec1, vec2, vec3; - - public void ApplyToMaterial(Material mat, int slotIndex) - { - var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_"; - mat.SetFloat(prefix + "Enabled", 1f); - mat.SetInt(prefix + "Opcode", opcode); - mat.SetFloat(prefix + "Float_0", float0); - mat.SetFloat(prefix + "Float_1", float1); - mat.SetFloat(prefix + "Float_2", float2); - mat.SetFloat(prefix + "Float_3", float3); - mat.SetVector(prefix + "Vector_0", vec0); - mat.SetVector(prefix + "Vector_1", vec1); - mat.SetVector(prefix + "Vector_2", vec2); - mat.SetVector(prefix + "Vector_3", vec3); - } -} - -public class VertexDeformationBuilder -{ - readonly List<VertexDeformationSlot> slots = new(); - Material targetMaterial; - - public static class Opcodes - { - public const int None = 0; - public const int TubeToPlane = 1; - public const int PlaneToTube = 2; - public const int PointAlign = 3; - public const int AxisAlign = 4; - public const int NormConversion = 5; - public const int Seal = 6; - public const int SineWaves = 7; - public const int FBM = 8; - } - - VertexDeformationBuilder() { } - - public static VertexDeformationBuilder Create() => new(); - - public VertexDeformationBuilder For(Material material) - { - targetMaterial = material; - return this; - } - - public VertexDeformationBuilder TubeToPlane(Vector3 p, Vector3 r, Vector3 s, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.TubeToPlane, - vec0 = p, - vec1 = r, - vec2 = s, - float0 = t - }); - return this; - } - - public VertexDeformationBuilder PlaneToTube(Vector3 p, Vector3 r, Vector3 s, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.PlaneToTube, - vec0 = p, - vec1 = r, - vec2 = s, - float0 = t - }); - return this; - } - - public VertexDeformationBuilder PointAlign(Vector3 po, Vector3 pp, Vector3 r, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.PointAlign, - vec0 = po, - vec1 = pp, - vec2 = r, - float0 = t - }); - return this; - } - - public VertexDeformationBuilder AxisAlign(Vector3 po, Vector3 pp, Vector3 r, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.AxisAlign, - vec0 = po, - vec1 = pp, - vec2 = r, - float0 = t - }); - return this; - } - - public VertexDeformationBuilder NormConversion(float inputK, float outputK, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.NormConversion, - float0 = inputK, - float1 = outputK, - float2 = t - }); - return this; - } - - public VertexDeformationBuilder Seal(float A, float k, float st, float t) - { - slots.Add(new VertexDeformationSlot - { - opcode = Opcodes.Seal, - float0 = A, - float1 = k, - float2 = st, - float3 = t - }); - return this; - } - - public VertexDeformationBuilder SineWaves() - { - slots.Add(new VertexDeformationSlot { opcode = Opcodes.SineWaves }); - return this; - } - - public VertexDeformationBuilder FBM() - { - slots.Add(new VertexDeformationSlot { opcode = Opcodes.FBM }); - return this; - } - - public VertexDeformationBuilder Custom(int opcode, - float f0 = 0, float f1 = 0, float f2 = 0, float f3 = 0, - Vector4? v0 = null, Vector4? v1 = null, Vector4? v2 = null, Vector4? v3 = null) - { - slots.Add(new VertexDeformationSlot - { - opcode = opcode, - float0 = f0, float1 = f1, float2 = f2, float3 = f3, - vec0 = v0 ?? Vector4.zero, - vec1 = v1 ?? Vector4.zero, - vec2 = v2 ?? Vector4.zero, - vec3 = v3 ?? Vector4.zero - }); - return this; - } - - public void Apply() - { - if (targetMaterial == null) - { - Debug.LogError("No target material set. Use .For(material) before .Apply()"); - return; - } - - if (slots.Count > 16) - { - Debug.LogWarning($"Too many operations ({slots.Count}). Only the first 16 will be applied."); - } - - Undo.RecordObject(targetMaterial, "Apply Vertex Deformation"); - - targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 1f); - - for (int i = 0; i < 16; i++) - { - if (i < slots.Count) - { - slots[i].ApplyToMaterial(targetMaterial, i); - } - else - { - var prefix = $"_Vertex_Deformation_Slot_{i}_"; - targetMaterial.SetFloat(prefix + "Enabled", 0f); - } - } - - EditorUtility.SetDirty(targetMaterial); - } - - public void Clear() - { - slots.Clear(); - } - - public int Count => slots.Count; -} - -public static class VertexDeformationPresets -{ - public static void TubeToPlaneFull(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .Apply(); - - public static void PlaneToTubeFull(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .Apply(); - - public static void TubeToPlaneThenNormConv(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .NormConversion(2f, 1f, 1f) - .Apply(); - - public static void NormConvL1ToL2(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .NormConversion(1f, 2f, 1f) - .Apply(); - - public static void NormConvL2ToL1(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .NormConversion(2f, 1f, 1f) - .Apply(); - - public static void NormConvL2ToLinf(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .NormConversion(2f, float.PositiveInfinity, 1f) - .Apply(); -} - -public class VertexDeformationEditorWindow : EditorWindow -{ - Material targetMaterial; - Vector2 scrollPos; - - [MenuItem("Window/Vertex Deformation Presets")] - static void ShowWindow() - { - GetWindow<VertexDeformationEditorWindow>("Vertex Deformation"); - } - - void OnGUI() - { - GUILayout.Label("Vertex Deformation Builder", EditorStyles.boldLabel); - - targetMaterial = EditorGUILayout.ObjectField("Target Material", targetMaterial, typeof(Material), false) as Material; - - if (targetMaterial == null) - { - EditorGUILayout.HelpBox("Select a material to apply vertex deformations", MessageType.Info); - return; - } - - scrollPos = EditorGUILayout.BeginScrollView(scrollPos); - - DrawSection("Basic Operations", () => - { - if (GUILayout.Button("Tube to Plane")) - VertexDeformationPresets.TubeToPlaneFull(targetMaterial); - - if (GUILayout.Button("Plane to Tube")) - VertexDeformationPresets.PlaneToTubeFull(targetMaterial); - }); - - DrawSection("Norm Conversions", () => - { - if (GUILayout.Button("L1 to L2")) - VertexDeformationPresets.NormConvL1ToL2(targetMaterial); - - if (GUILayout.Button("L2 to L1")) - VertexDeformationPresets.NormConvL2ToL1(targetMaterial); - - if (GUILayout.Button("L2 to L∞")) - VertexDeformationPresets.NormConvL2ToLinf(targetMaterial); - }); - - DrawSection("Complex Pipelines", () => - { - if (GUILayout.Button("Tube to Plane + Norm Conv (L2→L1)")) - VertexDeformationPresets.TubeToPlaneThenNormConv(targetMaterial); - }); - - DrawSection("Utilities", () => - { - if (GUILayout.Button("Clear All Deformations")) - { - Undo.RecordObject(targetMaterial, "Clear Vertex Deformations"); - targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f); - for (int i = 0; i < 16; i++) - { - var prefix = $"_Vertex_Deformation_Slot_{i}_"; - targetMaterial.SetFloat(prefix + "Enabled", 0f); - } - EditorUtility.SetDirty(targetMaterial); - } - - if (GUILayout.Button("Show Code Example")) - { - ShowCodeExample(); - } - }); - - EditorGUILayout.EndScrollView(); - } - - void DrawSection(string title, Action content) - { - GUILayout.Space(10); - GUILayout.Label(title, EditorStyles.boldLabel); - content(); - } - - void ShowCodeExample() - { - EditorUtility.DisplayDialog("Code Example", - "// Quick preset:\n" + - "VertexDeformationPresets.TubeToPlaneFull(material);\n\n" + - "// Custom pipeline:\n" + - "VertexDeformationBuilder.Create()\n" + - " .For(material)\n" + - " .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" + - " .NormConversion(2f, 1f, 1f)\n" + - " .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" + - " .Apply();\n\n" + - "// Add your own presets in VertexDeformationPresets class!", - "OK"); - } -} diff --git a/Scripts/Fold/Editor/VertexDeformationExamples.cs b/Scripts/Fold/Editor/VertexDeformationExamples.cs deleted file mode 100644 index 705cea9..0000000 --- a/Scripts/Fold/Editor/VertexDeformationExamples.cs +++ /dev/null @@ -1,114 +0,0 @@ -using UnityEditor; -using UnityEngine; - -public static class VertexDeformationExamples -{ - [MenuItem("Tools/Vertex Deformation/Example: Tube to Plane")] - static void Example_TubeToPlane() - { - var mat = Selection.activeObject as Material; - if (mat == null) - { - EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK"); - return; - } - - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane( - p: Vector3.zero, - r: Vector3.right, - s: Vector3.forward, - t: 1f) - .Apply(); - } - - [MenuItem("Tools/Vertex Deformation/Example: Complex Pipeline")] - static void Example_ComplexPipeline() - { - var mat = Selection.activeObject as Material; - if (mat == null) - { - EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK"); - return; - } - - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .NormConversion(inputK: 2f, outputK: 1f, t: 1f) - .PointAlign( - po: new Vector3(0, 0, 0), - pp: new Vector3(0, 1, 0), - r: Vector3.right, - t: 0.5f) - .Apply(); - - Debug.Log("Applied 3-step deformation pipeline to " + mat.name); - } - - [MenuItem("Tools/Vertex Deformation/Example: Add Custom Preset")] - static void Example_ShowHowToAddPreset() - { - EditorUtility.DisplayDialog("How to Add Presets", - "1. Open VertexDeformationPresets class\n\n" + - "2. Add a new static method:\n\n" + - "public static void MyCustomPreset(Material mat) =>\n" + - " VertexDeformationBuilder.Create()\n" + - " .For(mat)\n" + - " .TubeToPlane(...)\n" + - " .NormConversion(...)\n" + - " .Apply();\n\n" + - "3. Add a button in VertexDeformationEditorWindow:\n\n" + - "if (GUILayout.Button(\"My Custom Preset\"))\n" + - " VertexDeformationPresets.MyCustomPreset(targetMaterial);", - "OK"); - } - - [MenuItem("Tools/Vertex Deformation/Example: All Opcodes")] - static void Example_AllOpcodes() - { - EditorUtility.DisplayDialog("All Available Operations", - "TubeToPlane(p, r, s, t)\n" + - "PlaneToTube(p, r, s, t)\n" + - "PointAlign(po, pp, r, t)\n" + - "AxisAlign(po, pp, r, t)\n" + - "NormConversion(inputK, outputK, t)\n" + - "Seal(A, k, st, t)\n" + - "SineWaves()\n" + - "FBM()\n\n" + - "Custom(opcode, f0, f1, f2, f3, v0, v1, v2, v3)\n\n" + - "Chain them with method calls:\n" + - ".TubeToPlane(...).NormConversion(...).Apply()", - "OK"); - } - - public static class CustomPresetIdeas - { - public static void CylinderToPlane(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.up, Vector3.right, 1f) - .Apply(); - - public static void SphereToBox(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .NormConversion(inputK: 2f, outputK: float.PositiveInfinity, t: 1f) - .Apply(); - - public static void BoxToSphere(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .NormConversion(inputK: float.PositiveInfinity, outputK: 2f, t: 1f) - .Apply(); - - public static void TubeUnfoldAndFlatten(Material mat) => - VertexDeformationBuilder.Create() - .For(mat) - .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) - .NormConversion(inputK: 2f, outputK: 1f, t: 0.5f) - .Seal(A: 0.1f, k: 2f, st: 0.8f, t: 1f) - .Apply(); - } -} diff --git a/vertex.cginc b/vertex.cginc index ac2c6aa..e59813c 100644 --- a/vertex.cginc +++ b/vertex.cginc @@ -13,6 +13,7 @@ #define OPCODE_SEAL 6 #define OPCODE_SINE_WAVES 7 #define OPCODE_FBM 8 +#define OPCODE_PLANE_TO_HEMI_OCTAHEDRON 9 #if defined(_VERTEX_DEFORMATION) @@ -148,6 +149,22 @@ void apply_fbm_normal(inout float3 objPos, inout float3 objNorm, inout float3 ob fbm_normal(objPos, objNorm, objTan, st, amplitude, gain, lacunarity, period, octaves, velocity); } +void apply_plane_to_hemi_octahedron(inout float3 objPos, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + objPos = plane_to_hemi_octahedron(objPos, p, r, s, t); +} + +void apply_plane_to_hemi_octahedron_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTan, float4 v0, float4 v1, float4 v2, float f0) { + float3 p = v0.xyz; + float3 r = v1.xyz; + float3 s = v2.xyz; + float t = f0; + plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, p, r, s, t); +} + void deform(inout float3 objPos) { const float t = getTime(); @@ -380,6 +397,9 @@ void deform(inout float3 objPos) { case OPCODE_FBM: apply_fbm(objPos, v0, v1, v2, f0, f1, f2, t); break; + case OPCODE_PLANE_TO_HEMI_OCTAHEDRON: + apply_plane_to_hemi_octahedron(objPos, v0, v1, v2, f0); + break; } } } @@ -619,6 +639,9 @@ void deform_normal(inout float3 objPos, inout float3 objNorm, inout float3 objTa case OPCODE_FBM: apply_fbm_normal(objPos, objNorm, objTan, v0, v1, v2, f0, f1, f2, t); break; + case OPCODE_PLANE_TO_HEMI_OCTAHEDRON: + apply_plane_to_hemi_octahedron_normal(objPos, objNorm, objTan, v0, v1, v2, f0); + break; } } } diff --git a/vertex_deformation.slang b/vertex_deformation.slang index ed514fc..50c252e 100644 --- a/vertex_deformation.slang +++ b/vertex_deformation.slang @@ -3,6 +3,8 @@ #define PI 3.14159265f #define PI_RCP 0.31830988f +#define SQRT_2 1.41421356f +#define RCP_SQRT_2 0.70710678f #define TAU (2.0f * PI) #define HALF_PI (0.5f * PI) #define RCP_PI (1.0f / PI) @@ -11,8 +13,10 @@ #define glsl_mod(x,y) (((x)-(y)*floor((x)/(y)))) // Differentiable versions of common operators. -#define dabs(x) sqrt((x) * (x) + 1e-6) -#define dlerp(x, y, t) ((x) * (1-t) + (y) * t) +#define dabs(x) sqrt((x) * (x) + 1e-6f) +#define dmin(a, b) (0.5f * ((a) + (b) - dabs((a) - (b)))) +#define dmax(a, b) (0.5f * ((a) + (b) + dabs((a) - (b)))) +#define dlerp(x, y, t) ((x) * (1.0f-t) + (y) * t) // This was derived using fourier analysis. See Scripts/approximate.py. #define dfrac(x) \ 4.997559e-01 - \ @@ -558,4 +562,67 @@ public void fbm_undeform_normal(float3 xyz, float t, tangent = mul(inv_jac, tangent); } +// Maps a plane to a hemi-octahedron (half octahedron). +// Uses octahedral parameterization consistent with pbrt's OctahedralVector. +// Input: plane on [-1,1]² in the (r, rxs) plane +// Output: unit hemisphere with pole at +s direction +[Differentiable] +public float3 plane_to_hemi_octahedron(float3 xyz, + no_diff float3 p, no_diff float3 r_cart, no_diff float3 s_cart, + no_diff float t) { + // Convert from cartesian to (r, s, r x s) space. + r_cart = normalize(r_cart); + s_cart = normalize(s_cart); + float3 rxs_cart = cross(s_cart, r_cart); + float3x3 to_rsrxs = float3x3(r_cart, s_cart, rxs_cart); + float3x3 to_cart = transpose(to_rsrxs); + + // Translate origin to `p` then change into (r, s, r x s) basis. + xyz = mul(to_rsrxs, xyz - p); + + float3 xyz0 = xyz; + + // Extract planar coordinates: x and z form the 2D plane + float x = xyz.x; + float z = xyz.z; + + // Rotate 45° and scale to fit square into diamond + float x_rot = (x + z) * 0.5; + float z_rot = (z - x) * 0.5; + + // Octahedral decode: y = 1 - |x| - |z|, clamped to hemisphere + // Use differentiable abs and max for smooth autodiff + float y = dmax(0.0f, 1.0f - dabs(x_rot) - dabs(z_rot)); + + // Normalize to unit sphere (differentiable safe normalize) + float3 oct_pos = float3(x_rot, y, z_rot); + float len = dot(oct_pos, oct_pos); + oct_pos = oct_pos / sqrt(len); + + // Rotate back by -45° around y to undo input rotation + float x_unrot = (oct_pos.x - oct_pos.z) * RCP_SQRT_2; + float z_unrot = (oct_pos.x + oct_pos.z) * RCP_SQRT_2; + oct_pos = float3(x_unrot, oct_pos.y, z_unrot); + + // Interpolate between original position and sphere position + float3 result = dlerp(xyz0, oct_pos, dmin(t, 1.0f)); + + // Map back to cartesian basis + xyz = mul(to_cart, result) + p; + + return xyz; +} + +public void plane_to_hemi_octahedron_normal(inout float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 r, float3 s, float t) { + R3R3_NORMALS(xyz, normal, tangent, plane_to_hemi_octahedron, p, r, s, t); +} + +public void plane_to_hemi_octahedron_undeform_normal(float3 xyz, inout float3 normal, + inout float3 tangent, float3 p, float3 r, float3 s, float t) { + R3R3_DECLARE_BASIS_VECTORS(xyz); + R3R3_AUTODIFF_BASIS_VECTORS(plane_to_hemi_octahedron, p, r, s, t); + R3R3_UNDEFORM_NORMAL_AND_TANGENT(normal, tangent); +} + #endif // __CUSTOM31_INC |
